Skip to content

Commit b50b850

Browse files
alinearonskyclaude
andcommitted
docs: remove em dashes project-wide; refresh OG social image
Drop 'the waitlist that moves' from the OG card and regenerate public/og.png (tagline now 'Refer friends. Skip the line.'). Sweep every em dash out of source, styles, and docs (README, DESIGN, CONTRIBUTING, supabase, LAUNCH), restructuring prose with proper punctuation rather than mechanical commas. Lint, tsc, and 73 unit tests all pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 0327217 commit b50b850

35 files changed

Lines changed: 132 additions & 132 deletions

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Contributing to RefQueue
22

3-
Thanks for helping issues and PRs are welcome.
3+
Thanks for helping, issues and PRs are welcome.
44

55
## Development setup
66

DESIGN.md

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ The single source of truth for RefQueue's public pages (landing + status). Maker
44
screens (dashboard, login) stay neutral (Geist) and are out of scope. Every color, size, and
55
spacing value on the public pages traces to a token here.
66

7-
**Direction "Admit One":** a **vintage theatre-ticket playbill**. An aged programme sheet
8-
pinned to a board letterpress type, playbill vermilion, double-ruled borders, film-grain and a
9-
warm vignette with a **real scanned Admit One ticket stub** as the hero object. The number is
7+
**Direction, "Admit One":** a **vintage theatre-ticket playbill**. An aged programme sheet
8+
pinned to a board, letterpress type, playbill vermilion, double-ruled borders, film-grain and a
9+
warm vignette, with a **real scanned Admit One ticket stub** as the hero object. The number is
1010
the star: a numbered ticket you hold, then move toward the front of the house. Committed,
11-
nostalgic, and tactile deliberately *not* the warm-cream editorial or dark-neon AI defaults.
11+
nostalgic, and tactile, deliberately *not* the warm-cream editorial or dark-neon AI defaults.
1212

13-
**Scope note theming:** the accent is a *default* (playbill red `--red`). Makers override it
13+
**Scope note, theming:** the accent is a *default* (playbill red `--red`). Makers override it
1414
with `THEME_ACCENT_COLOR`, which sets `--accent`/`--accent-text` inline on `<main>` and wins over
15-
the defaults below. The accent lives in the **interactive + emphasis layer** CTA fill, the
16-
kicker, the ticket border, the position numeral, links, small marks so a maker's brand color
15+
the defaults below. The accent lives in the **interactive + emphasis layer**: CTA fill, the
16+
kicker, the ticket border, the position numeral, links, small marks, so a maker's brand color
1717
flows through the whole piece and still reads on aged paper (pick a saturated mid-tone).
1818

1919
---
@@ -22,21 +22,21 @@ flows through the whole piece and still reads on aged paper (pick a saturated mi
2222

2323
RefQueue reads like a **printed variety-show programme**: an aged paper sheet, a double-ruled
2424
frame, a hand-set letterpress headline, and a torn ticket stub. The drama
25-
is **material** grain, vignette, ink-on-paper contrast, and one saturated red. Type carries the
25+
is **material**: grain, vignette, ink-on-paper contrast, and one saturated red. Type carries the
2626
period: an aged display face (Rye) for the headline and serials, a condensed grotesque (Oswald)
2727
for uppercase utility lettering, and a slab serif (Zilla Slab) for programme body copy.
2828

2929
**Signature:** the **numbered ticket**. On the landing it is a real scanned *REFQUEUE Admit One*
3030
stub (serial No 00247) beside a huge `No. 247` headline. On the status page it becomes **your live
31-
position printed as a serial** `No. 41` set huge in Rye red inside a bordered stub.
31+
position printed as a serial**: `No. 41` set huge in Rye red inside a bordered stub.
3232

3333
---
3434

3535
## 2. Color
3636

3737
Aged programme paper is the canvas; sepia-black is the ink; playbill red is the one saturated
3838
voice. The board behind the sheet is a deeper tan. The accent (red by default) carries CTA, the
39-
ticket border, and the numeral never scattered as decoration.
39+
ticket border, and the numeral, never scattered as decoration.
4040

4141
| Role | Token | Value | Usage |
4242
|---|---|---|---|
@@ -45,10 +45,10 @@ ticket border, and the numeral — never scattered as decoration.
4545
| Paper (foot) | `--paper-2` | `#e6d8ba` | Deeper paper at the foot of the sheet gradient |
4646
| Paper raised | `--paper-raised` | `#f1e7cf` | Input wells, card / ticket tops |
4747
| Paper sunken | `--paper-sunken` | `#e2d3b0` | Sunken wells (referral link) |
48-
| Ink | `--ink` | `#241c14` | Sepia-black letterpress headlines, body, frame |
49-
| Ink soft | `--ink-soft` | `#5a4b38` | Faded ink subheads, captions |
48+
| Ink | `--ink` | `#241c14` | Sepia-black letterpress, headlines, body, frame |
49+
| Ink soft | `--ink-soft` | `#5a4b38` | Faded ink, subheads, captions |
5050
| Ink faint | `--ink-faint` | `#8a7a5c` | Fine print, meta |
51-
| Red (accent default) | `--red` | `#b5321f` | Playbill vermilion CTA, kicker, ticket, numeral |
51+
| Red (accent default) | `--red` | `#b5321f` | Playbill vermilion, CTA, kicker, ticket, numeral |
5252
| Blue | `--blue` | `#2f4d6b` | Ink-blue, used sparingly |
5353
| Line | `--line` | `#c3ad84` | Aged hairline rules |
5454
| Line strong | `--line-strong` | `#b49b6d` | Card / well borders |
@@ -60,18 +60,18 @@ ticket border, and the numeral — never scattered as decoration.
6060

6161
### Rules
6262
- The sheet always carries **grain + a warm vignette** (`.rq-sheet::before/::after`). That texture
63-
is the anti-AI signal do not flatten it to a solid fill.
63+
is the anti-AI signal, do not flatten it to a solid fill.
6464
- `--accent` is interactive/emphasis only (CTA, ticket border, kicker, numeral, links). Default is
6565
playbill red; a maker's `THEME_ACCENT_COLOR` overrides it and must stay saturated enough to read
6666
as ink on aged paper.
67-
- Body text is `--ink` / `--ink-soft` on paper both clear ≥4.5:1. Fine print (`--ink-faint`) is
67+
- Body text is `--ink` / `--ink-soft` on paper, both clear ≥4.5:1. Fine print (`--ink-faint`) is
6868
reserved for genuinely secondary lines (letterhead flourish, stub fine print).
6969

7070
---
7171

7272
## 3. Typography
7373

74-
Five families via `next/font` in `layout.tsx` three for the public surface, Geist Sans/Mono
74+
Five families via `next/font` in `layout.tsx`, three for the public surface, Geist Sans/Mono
7575
retained for the maker dashboard.
7676

7777
| Level | Family | Size (clamp) | Weight | Tracking | Usage |
@@ -91,7 +91,7 @@ retained for the maker dashboard.
9191
- Mono: `var(--font-geist-mono), ui-monospace, monospace`
9292

9393
### Rules
94-
- **Rye is for statements only** (headline, serials, section heads). Its numerals are chunky
94+
- **Rye is for statements only** (headline, serials, section heads). Its numerals are chunky,
9595
always give multi-digit numbers ≥0.06em tracking so digits don't kiss (serial uses 0.09em).
9696
- Oswald caps carry all uppercase utility lettering (letterhead, kicker, CTA, labels). Never set
9797
Rye in a running label.
@@ -116,7 +116,7 @@ Base unit **4px**. The public page is a centered **sheet** floating on the board
116116

117117
### Rules
118118
- Every spacing value maps to a token. No magic numbers.
119-
- The frame + rules are structural, not decoration they set the programme rhythm.
119+
- The frame + rules are structural, not decoration, they set the programme rhythm.
120120

121121
---
122122

@@ -128,15 +128,15 @@ Base unit **4px**. The public page is a centered **sheet** floating on the board
128128
- **States:** static; entrance = fade + rise (`.rq-enter`), reduced-motion safe.
129129

130130
### Letterhead (status only)
131-
- **Structure:** an Oswald-caps masthead a single centered `★ The {name} Variety Co. ★`
131+
- **Structure:** an Oswald-caps masthead, a single centered `★ The {name} Variety Co. ★`
132132
followed by a thick double rule. The landing has **no** letterhead; it opens straight on the hero.
133133

134134
### Hero headline (landing)
135-
- **Structure:** the Rye `<h1>` alone default **"No. 247"** with `247` in `--accent`, set large
136-
(`clamp(56px, 10vw, 118px)`) then a Zilla Slab lede. No kicker. `theme.headline` /
135+
- **Structure:** the Rye `<h1>` alone, default **"No. 247"** with `247` in `--accent`, set large
136+
(`clamp(56px, 10vw, 118px)`), then a Zilla Slab lede. No kicker. `theme.headline` /
137137
`theme.subhead` override the defaults.
138138

139-
### Ticket stub landing
139+
### Ticket stub, landing
140140
- **Structure:** the real scanned *REFQUEUE Admit One* cutout (`/ticket-admit-one.webp`,
141141
transparent WebP keyed from a generated ticket, ~85KB), rotated 4.5°. Decorative; descriptive
142142
`alt` on the image.
@@ -150,12 +150,12 @@ Base unit **4px**. The public page is a centered **sheet** floating on the board
150150
- **Accessibility:** `aria-label` on the input; real submit button; visible focus.
151151

152152
### Acts (landing "how it works")
153-
- **Structure:** three columns Rye red numerals `I. / II. / III.`, an Oswald caps title, Zilla
153+
- **Structure:** three columns, Rye red numerals `I. / II. / III.`, an Oswald caps title, Zilla
154154
Slab copy (Join the line → Send your friends → Move up the line). Stacks ≤760px.
155155

156-
### Ticket stub status (signature)
156+
### Ticket stub, status (signature)
157157
- **Structure:** a bordered stub (`--accent` border + inner dashed rule) holding: an Oswald overline
158-
("Your position on {name}"), "ADMIT ONE IN LINE" with flanking rules (rules hidden on mobile),
158+
("Your position on {name}"), "ADMIT ONE, IN LINE" with flanking rules (rules hidden on mobile),
159159
the **live position as a Rye red serial** (`No. 41`), and the referral-count line.
160160
- **States:** static now; the stub is the anchor for the wave-two position-jump animation.
161161
- **Accessibility:** the number is real text; the frame/pointer are decorative.
@@ -180,20 +180,20 @@ Base unit **4px**. The public page is a centered **sheet** floating on the board
180180
| Standard | 480ms | cubic-bezier(0.16,1,0.3,1) | Sheet entrance (fade + rise) |
181181

182182
### Rules
183-
- **GPU-composited only** `transform`, `opacity`, `filter`, `box-shadow`. Never animate layout.
183+
- **GPU-composited only**: `transform`, `opacity`, `filter`, `box-shadow`. Never animate layout.
184184
- Respect `prefers-reduced-motion`: entrance becomes instant.
185-
- Hover on the CTA brightens the ink slightly and presses on active no glow.
185+
- Hover on the CTA brightens the ink slightly and presses on active, no glow.
186186

187187
---
188188

189189
## 7. Accepted debt / deferred
190190

191191
- **Position-jump animation** → wave two (its own mini-plan). The status ticket stub is its anchor.
192-
- **Rye serial isn't tabular** acceptable for a static number; the ≥0.09em tracking keeps digits
192+
- **Rye serial isn't tabular**: acceptable for a static number; the ≥0.09em tracking keeps digits
193193
legible. Digit alignment handled in the animation mini-plan.
194-
- **Light-only** the public pages are always the aged-paper surface (the designed screenshot/video
194+
- **Light-only**: the public pages are always the aged-paper surface (the designed screenshot/video
195195
surface). No dark variant; grain + vignette + red supply the drama.
196-
- **Landing ticket is a fixed demo serial (REFQUEUE · No 00247)** a scanned image, so it does not
196+
- **Landing ticket is a fixed demo serial (REFQUEUE · No 00247)**: a scanned image, so it does not
197197
reflect a visitor's real number (they have none yet). The *status* stub is the live, dynamic serial.
198-
- **Full Lighthouse-100 audit + react-scan tooling** not run this pass (SSR, near-zero client JS,
198+
- **Full Lighthouse-100 audit + react-scan tooling**: not run this pass (SSR, near-zero client JS,
199199
one ~92KB image, fast by architecture). Revisit if needed.

README.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# RefQueue
22

33
**Open-source waitlist with built-in referrals.** The "refer friends to skip the line"
4-
mechanic behind Superhuman's and Robinhood's launches the one GetWaitlist and Viral
5-
Loops paywall at $35–50/mo — free, self-hosted, and yours.
4+
mechanic behind Superhuman's and Robinhood's launches, the one GetWaitlist and Viral
5+
Loops paywall at $35–50/mo. Free, self-hosted, and yours.
66

7-
![RefQueue join the waitlist, get a numbered ticket, refer friends to move up the line](docs/demo.gif)
7+
![RefQueue, join the waitlist, get a numbered ticket, refer friends to move up the line](docs/demo.gif)
88

99
They took the free plan away and the category leader just got acquired. Here's the
1010
open-source one that can't be paywalled or taken from you.
@@ -13,25 +13,25 @@ open-source one that can't be paywalled or taken from you.
1313

1414
- **Email signup → a live queue position** ("You're #247").
1515
- **A unique referral link per signup** with X / WhatsApp / LinkedIn / email share buttons.
16-
- **Confirmed referrals move you up the line** every friend who joins *and confirms
16+
- **Confirmed referrals move you up the line**: every friend who joins *and confirms
1717
their email* bumps your position. Unconfirmed signups never count.
1818
- **Configurable reward milestones** ("refer 3 → early access") with automatic emails.
19-
- **A maker dashboard** signups, positions, top referrers, a 30-day chart, CSV export.
20-
- **Themeable** logo, color, and copy via environment variables.
21-
- **"Powered by RefQueue" credit** on by default, removable with one env var.
19+
- **A maker dashboard**: signups, positions, top referrers, a 30-day chart, CSV export.
20+
- **Themeable**: logo, color, and copy via environment variables.
21+
- **"Powered by RefQueue" credit**: on by default, removable with one env var.
2222

2323
![Status page](docs/screenshots/status.png)
2424

2525
## Anti-gaming
2626

2727
A referral counts **only after the referred email completes double opt-in.** That makes a
28-
fake referral cost a real, verifiable inbox the single rule that keeps the numbers honest.
28+
fake referral cost a real, verifiable inbox, the single rule that keeps the numbers honest.
2929
Per-IP rate limiting and disposable-email blocking back it up.
3030

3131
## Quick start
3232

3333
RefQueue is a Next.js app backed by [Supabase](https://supabase.com) (Postgres + Auth).
34-
Set up Supabase first see [`supabase/README.md`](supabase/README.md) then deploy.
34+
Set up Supabase first (see [`supabase/README.md`](supabase/README.md)), then deploy.
3535

3636
### Deploy to Vercel
3737

@@ -51,15 +51,15 @@ the public URL.
5151

5252
## Configuration
5353

54-
All configuration is environment variables no settings UI to break. Copy `.env.example`
54+
All configuration is environment variables, no settings UI to break. Copy `.env.example`
5555
and fill in. The app **refuses to start in production** if a required variable is missing.
5656

5757
| Variable | Required | What it's for |
5858
|---|---|---|
5959
| `SUPABASE_URL` | yes | Supabase project URL |
6060
| `SUPABASE_SERVICE_ROLE_KEY` | yes | Server-side data access (keep secret) |
6161
| `SUPABASE_ANON_KEY` | yes | Maker auth sessions |
62-
| `APP_BASE_URL` | yes | Your public URL used in verification links |
62+
| `APP_BASE_URL` | yes | Your public URL, used in verification links |
6363
| `EMAIL_FROM` | yes | From-address for emails |
6464
| `RESEND_API_KEY` *or* `SMTP_*` | yes | An email provider (double-opt-in needs one) |
6565
| `WAITLIST_NAME` | no | Display name (default "Waitlist") |
@@ -85,26 +85,26 @@ npm run test:integration # needs the local Supabase stack running
8585

8686
RefQueue v1 is deliberately small. Documented tradeoffs (tracked for v1.1):
8787

88-
- **Rate limiting is per-instance** and keyed on `x-forwarded-for` — deploy behind a proxy
88+
- **Rate limiting is per-instance** and keyed on `x-forwarded-for`. Deploy behind a proxy
8989
that sets it, and expect a shared limiter to arrive with multi-instance support.
9090
- **A milestone reward email can rarely double-send** if two referred users confirm in the
9191
same instant (best-effort semantics; bounded to a duplicate email, never a broken flow).
92-
- **Re-signing up with a known email returns that email's status** convenient status
92+
- **Re-signing up with a known email returns that email's status**: convenient status
9393
read-back, but it discloses membership. Use a generic response if that matters to you.
94-
- **The dashboard loads all signups per view** fine to tens of thousands; revisit at scale.
94+
- **The dashboard loads all signups per view**: fine to tens of thousands; revisit at scale.
9595

9696
## Roadmap (v1.1+)
9797

98-
- **Embeddable widget** drop the signup form into an existing site (most-requested).
99-
- **Position-jump animation** the satisfying "move up the line" motion.
98+
- **Embeddable widget**: drop the signup form into an existing site (most-requested).
99+
- **Position-jump animation**: the satisfying "move up the line" motion.
100100
- Email-provider sync (Mailchimp/ConvertKit), shared/distributed rate limiting, generated
101101
Supabase types.
102102

103103
## Contributing
104104

105-
Issues and PRs welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
105+
Issues and PRs welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
106106

107107
## License
108108

109-
[MIT](LICENSE) — an open-source alternative to GetWaitlist / Viral Loops. Own implementation;
109+
[MIT](LICENSE). An open-source alternative to GetWaitlist / Viral Loops. Own implementation;
110110
no affiliation.

public/og.png

-13 KB
Loading

src/app/PoweredBy.module.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Sits inside the sheet footer on public pages the printed credit line. */
1+
/* Sits inside the sheet footer on public pages, the printed credit line. */
22
.credit {
33
font-family: var(--font-oswald), system-ui, sans-serif;
44
text-transform: uppercase;

src/app/PoweredBy.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const REPO_URL = 'https://github.com/alinearonsky/refqueue'
44

55
/**
66
* The self-distribution credit (PRODUCT.md): on by default on every public
7-
* page, removable via POWERED_BY=false forcing it would be user-hostile.
7+
* page, removable via POWERED_BY=false, forcing it would be user-hostile.
88
*/
99
export function PoweredBy({ enabled }: { enabled: boolean }) {
1010
if (!enabled) return null

src/app/SignupForm.module.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
font-size: 0.9rem;
7878
}
7979

80-
/* Confirmation reads like a torn-off stub receipt. */
80+
/* Confirmation, reads like a torn-off stub receipt. */
8181
.success {
8282
font-family: var(--font-zilla), Georgia, serif;
8383
font-size: 1.05rem;

src/app/SignupForm.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ interface SignupResult {
1111

1212
const ERROR_MESSAGES: Record<string, string> = {
1313
invalid_input: 'That doesn’t look like a valid email address.',
14-
disposable_email: 'Disposable email addresses aren’t accepted — please use your real one.',
14+
disposable_email: 'Disposable email addresses aren’t accepted. Please use your real one.',
1515
rate_limited: 'Too many signups from your network right now. Try again in a few minutes.',
1616
waitlist_not_found: 'This waitlist isn’t set up yet. Try again shortly.',
1717
}
@@ -64,7 +64,7 @@ export function SignupForm({
6464
) : (
6565
<p className={styles.success}>
6666
You’re <strong className={styles.pos}>#{result.position}</strong> in line. Check your inbox and confirm your
67-
email to lock in your spot — that’s where your referral link lives.
67+
email to lock in your spot. That’s where your referral link lives.
6868
</p>
6969
)
7070
}

src/app/accent.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import type { CSSProperties } from 'react'
22
import type { ThemeConfig } from '@/lib/config'
33

4-
const CREAM = '#f7ecd2' // brand cream text on a dark accent
5-
const DARK = '#241c14' // sepia ink text on a light accent
4+
const CREAM = '#f7ecd2' // brand cream, text on a dark accent
5+
const DARK = '#241c14' // sepia ink, text on a light accent
66

77
function parseHex(color: string): [number, number, number] | null {
88
const m = color.trim().replace(/^#/, '')
@@ -33,7 +33,7 @@ function contrast(a: number, b: number): number {
3333
* Inline CSS custom properties carrying the maker's accent. Consumed via
3434
* var(--accent) / var(--accent-text), so an unset accent renders the default
3535
* playbill red. `--accent-text` is chosen for contrast: whichever of cream /
36-
* sepia-ink reads better on the accent fill so a maker's light OR dark brand
36+
* sepia-ink reads better on the accent fill, so a maker's light OR dark brand
3737
* color keeps a legible CTA (a light accent no longer forces unreadable white).
3838
*/
3939
export function accentStyle(theme: ThemeConfig): CSSProperties | undefined {

src/app/api/dashboard/export/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export async function GET() {
2323
headers: {
2424
'Content-Type': 'text/csv; charset=utf-8',
2525
'Content-Disposition': 'attachment; filename="signups.csv"',
26-
// Bulk PII download never let a browser or intermediary cache it.
26+
// Bulk PII download, never let a browser or intermediary cache it.
2727
'Cache-Control': 'no-store',
2828
},
2929
})

0 commit comments

Comments
 (0)