l3gacyb3ta/testtest99

★ 0Forks 0TypeScriptGitHub ↗Compare

README

Half Life

Hack Club's ten-week hardware programme. One repository, two worlds that are meant to look nothing alike:

  • The landing page (src/app/(site)) — the marketing frame, built to the Figma "landing page (claude)" (275:8) with the palette from "color scheme" (275:222).
  • The platform (src/app/(platform)) — the logged-in product: a Duolingo-style trail of checkpoints, a journal, a shop, and the Doomscroller. Documented in docs/platform-ui.md.

Signed out, / is the landing page. Signed in, proxy.ts rewrites it to the trail: two route groups cannot both define /, and each has its own root layout and stylesheet. The cookie check there is optimistic and decides nothing that matters — the real session guard is a server component.

pnpm install
./dev.sh      # Postgres in Docker, migrated and seeded, then next dev

New here? docs/BACKEND.md is the handoff — the shape, the invariants, and the traps that have already bitten. Beyond it: the visual system is DESIGN.md, the reasoning behind the invariants is docs/ARCHITECTURE.md, and operating it is docs/RUNBOOK.md.

What's where

src/app/(site)/               the landing page
src/app/(platform)/(app)/     the trail, projects, shop, explore, leaderboard
src/app/(platform)/(ops)/     admin, review, login — earlier design system
src/app/(handoff)/            the phone upload page a QR code opens
src/app/api/                  every write in the product
src/components/site/          landing sections
src/components/platform/      the logged-in product's components
src/lib/config/               every tunable number: tiers, printers, programme
src/lib/curriculum.ts         the shape of a week, as a pure function
src/lib/queries/store.ts      the trail's data, read in one pass
src/lib/store.tsx             that snapshot, plus every write the trail makes
prisma/schema.prisma          the data model
public/art/                   hero painting, scatter photos, comp SVG exports

Checking it still works

pnpm typecheck && pnpm lint && pnpm build
pnpm verify:ledger      # the economy: two pots, and every printer reachable
pnpm verify:store       # the database-to-trail mapping
pnpm verify:onboarding  # the first checkpoint cannot be skipped
pnpm verify:feed        # moderation actually removes things
pnpm verify:handoff     # the QR upload token is single-use

The verify scripts write to whatever DATABASE_URL points at and clean up after themselves. Point them at a scratch database, never production.

Things you'll want to swap

1. The logo

src/components/wordmark.tsx sets the wordmark in live type by default. Drop your artwork in public/brand/ and set one constant:

const LOGO_SRC = "/brand/half-life-wordmark.svg";
const LOGO_ASPECT = 981 / 261; // your file's width ÷ height

The comp reserves a 981 × 261 box. The slot fills its width either way, so nothing else changes.

2. The step photos and project photos

Every reserved photo is an <ImageSlot> — an ink plate with cyan registration ticks and the artwork's native size printed in it, so an empty slot reads as a deliberate footprint rather than a broken image. Each one tells you its size:

Where Native size File
Step 2 — "get funding…" 736 × 155 src/components/process.tsx
Step 3 — "then spend 5 weeks…" 736 × 269 src/components/process.tsx
Step 4 — "get a 3D printer!" 736 × 269 src/components/process.tsx
Carousel cards (×5) 329 × 377 src/lib/content.ts → BUILD_CARDS

Replace an <ImageSlot …/> with <Image …/> in the same wrapper — the box is already the comp's box.

3. Where the emails go

POST /api/subscribe validates, normalises and de-duplicates, then writes to one of two sinks:

Env Behaviour
SIGNUP_WEBHOOK_URL POST { email, source, at } to your list provider or relay
SIGNUP_WEBHOOK_TOKEN optional Authorization: Bearer … for that webhook
(neither set) appends JSON Lines to ./data/signups.jsonl (gitignored)

The file sink is fine for local development and any host with a writable disk. It is not durable on serverless — point SIGNUP_WEBHOOK_URL at a real provider before launch. The rate limit is per-instance and coarse; put Vercel BotID or a WAF rule in front of the route for real abuse protection.

4. The Slack bot

src/app/api/slack/events/ and src/app/api/slack/interactions/ back a bot that:

  • invites anyone who joins the main Half Life channel into #halflife-bulletin and #halflife-help
  • posts every new top-level message in #halflife-help into a private tickets channel with a Mark as helped button (anyone with access to that channel can click it), and replies in-thread in #halflife-help with an I'm all set button that only the ticket's own author can click

Either button resolves the ticket: it edits its own message in place, and — for the author's button — also updates the tickets-channel message so staff can see it's already handled. There's no database; each button's own value carries the pointer(s) it needs back to the other message(s). One known gap: if staff resolve a ticket first, the author's button is left active and clicking it afterward will just re-mark things as resolved by the author — harmless, but slightly confusing if it happens.

Create the app from slack-app-manifest.yml at the repo root (see the comments in that file for the exact steps), then set:

Env What
SLACK_BOT_TOKEN Bot User OAuth Token (xoxb-…)
SLACK_SIGNING_SECRET Basic Information → Signing Secret, verifies both routes
SLACK_MAIN_CHANNEL_ID channel that triggers the auto-invite
SLACK_BULLETIN_CHANNEL_ID #halflife-bulletin's channel ID
SLACK_HELP_CHANNEL_ID #halflife-help's channel ID
SLACK_TICKETS_CHANNEL_ID private channel new/resolved tickets get posted to

The bot needs to be a member of all four channels — invite it manually after installing.

5. Facts to confirm before launch

  • The carousel credits are the comp's placeholder credit ("by Meghana, 17, from Ohio") repeated across five cards — replace with the real makers.
  • Hack Club's EIN (81-2908499) is in the footer. Confirm it and decide whether you also want the mailing address, which the Hack Club footer carries.
  • FAQ card six ("What do I get?") replaces a duplicated "What's Hack Club?" in the comp. Its copy is assembled from facts the comp already states.

Responsive behaviour

Two layouts, on purpose:

  • ≥ 1180px — .stage reproduces the comp's 1728px grid exactly. Every offset is written as its literal Figma pixel and resolved in cqw, so the composition scales continuously instead of snapping.
  • < 1180px — the same content stacked, with the connector bands re-drawn as short vertical tapers between plates.

Contributors

l3gacyb3taMeghanaM4

Issues