Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
1cb21a2
Add bracket predictions with retro-styled login card
sergical Jan 17, 2026
8373334
Separate public bracket from interactive bracket
sergical Jan 19, 2026
432ecbd
Fix reset predictions and bracket positioning
sergical Jan 19, 2026
d4b894b
Consolidate Bracket components with isInteractive flag
sergical Jan 19, 2026
64583c3
Add OAuth redirect to bracket and update docs
sergical Jan 19, 2026
bfaeb92
package manager
sergical Jan 19, 2026
e7df218
Add retro scoreboard countdown component
sergical Jan 19, 2026
7a17746
Add retro leaderboard component
sergical Jan 19, 2026
a665a0c
Add DB schema and integrate leaderboard
sergical Jan 19, 2026
27b6335
Add scoreboard countdown and login improvements
sergical Jan 19, 2026
1de9efb
Add bracket sharing, OG images, and UI improvements
sergical Jan 26, 2026
9bac282
Fix Drizzle query to filter by both userId and gameId
sergical Jan 26, 2026
21112bd
Add bracket styling: React Flow controls and champion stacking
sergical Jan 19, 2026
6e422e4
Add retro scoreboard countdown component
sergical Jan 19, 2026
a9669ca
Fix bracket CSS: finalist selector and photo size
sergical Jan 26, 2026
37fb74a
Clean up PR: consolidate migrations and remove unused code
sergical Jan 26, 2026
ffbbd91
Fix biome lint: disable !important rule and add ignore for digit keys
sergical Jan 26, 2026
9d6265d
Improve bracket visuals and prediction feedback
sergical Jan 26, 2026
047ae4b
Enlarge OG image avatars and expand bracket layout
sergical Jan 26, 2026
a0eedf3
Simplify share bracket page and use notFound for errors
sergical Jan 26, 2026
65bfd2c
Fix bracket CSS selector to match node IDs
sergical Jan 26, 2026
f83058b
Add badge CSS for selected player checkmarks
sergical Jan 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 60 additions & 18 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,41 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This file provides guidance to Claude Code (claude.ai/code) when working with
code in this repository.

## Project Overview

Mad CSS is a TanStack Start application for "The Ultimate CSS Tournament" - an event website featuring 16 developers battling for CSS glory. Built with React 19, TanStack Router, and deploys to Cloudflare Workers.
Mad CSS is a TanStack Start application for "The Ultimate CSS Tournament" - an
event website featuring 16 developers battling for CSS glory. Built with React
19, TanStack Router, and deploys to Cloudflare Workers.

## Commands

**Package manager: pnpm**

```bash
# Development
npm run dev # Start dev server on port 3000
pnpm dev # Start dev server on port 3000
[Note] I will run the dev command myself unless otherwise specified

# Build & Deploy
npm run build # Build for production
npm run deploy # Build and deploy to Cloudflare Workers
pnpm build # Build for production
pnpm deploy # Build and deploy to Cloudflare Workers

# Code Quality
npm run check # Run Biome linter and formatter checks
npm run lint # Lint only
npm run format # Format only
pnpm check # Run Biome linter and formatter checks
pnpm lint # Lint only
pnpm format # Format only

# Testing
npm run test # Run Vitest tests
pnpm test # Run Vitest tests

# Database
npm run db:generate # Generate Drizzle migrations from schema
npm run db:migrate:local # Apply migrations to local D1
npm run db:migrate:prod # Apply migrations to production D1
npm run db:studio # Open Drizzle Studio
npm run db:setup # Generate + migrate local (full setup)
pnpm db:generate # Generate Drizzle migrations from schema
pnpm db:migrate:local # Apply migrations to local D1
pnpm db:migrate:prod # Apply migrations to production D1
pnpm db:studio # Open Drizzle Studio
pnpm db:setup # Generate + migrate local (full setup)
```

## Database Setup
Expand Down Expand Up @@ -66,6 +72,20 @@ BETTER_AUTH_SECRET=your_random_secret
BETTER_AUTH_URL=http://localhost:3000
```

### GitHub OAuth Setup

1. Go to GitHub Settings > Developer settings > OAuth Apps > New OAuth App
2. Fill in:
- **Application name:** Mad CSS (Local) or similar
- **Homepage URL:** `http://localhost:3000/test`
- **Authorization callback URL:**
`http://localhost:3000/api/auth/callback/github`
3. Click "Register application"
4. Copy the **Client ID** to `GITHUB_CLIENT_ID` in `.dev.vars`
5. Generate a new **Client Secret** and copy to `GITHUB_CLIENT_SECRET`

The login flow redirects to `/test` after authentication.

### Production Deployment

1. Set secrets in Cloudflare dashboard (Workers > Settings > Variables):
Expand All @@ -82,21 +102,43 @@ BETTER_AUTH_URL=http://localhost:3000

**Stack:** TanStack Start (SSR framework) + React 19 + Vite + Cloudflare Workers

**File-based routing:** Routes live in `src/routes/`. TanStack Router auto-generates `src/routeTree.gen.ts` - don't edit this file manually.
**File-based routing:** Routes live in `src/routes/`. TanStack Router
auto-generates `src/routeTree.gen.ts` - don't edit this file manually.

**Key directories:**

- `src/routes/` - Page components and API routes
- `src/routes/__root.tsx` - Root layout, includes Header and devtools
- `src/components/` - Reusable components (Header, Ticket, Roster)
- `src/components/` - Reusable components (Header, Ticket, LoginSection,
bracket/, roster/, footer/, rules/)
- `src/lib/` - Auth setup (better-auth) and utilities (cfImage.ts for Cloudflare
Images)
- `src/data/` - Player data (players.ts with 16 contestants)
- `src/styles/` - CSS files imported directly into components
- `public/` - Static assets (logos, images)
- `public/` - Static assets (logos, images, card artwork)

**Path alias:** `@/*` maps to `./src/*`

**Styling:** Plain CSS with CSS custom properties defined in `src/styles/styles.css`. Uses custom fonts (Kaltjer, CollegiateBlackFLF, Inter) and texture backgrounds.
**Styling:** Plain CSS with CSS custom properties defined in
`src/styles/styles.css`. Uses custom fonts (Kaltjer, CollegiateBlackFLF, Inter)
and texture backgrounds.

## Code Style

- Biome for linting/formatting (tabs, double quotes)
- TypeScript strict mode
- XY Flow library for tournament bracket visualization

## Comment Policy

### Unacceptable Comments

- Comments that repeat what code does
- Commented-out code (delete it)
- Obvious comments ("increment counter")
- Comments instead of good naming

### Principle

Code should be self-documenting. If you need a comment to explain WHAT the code
does, consider refactoring to make it clearer.
5 changes: 4 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@
"linter": {
"enabled": true,
"rules": {
"recommended": true
"recommended": true,
"complexity": {
"noImportantStyles": "off"
}
}
},
"javascript": {
Expand Down
53 changes: 0 additions & 53 deletions drizzle/0000_rare_juggernaut.sql

This file was deleted.

104 changes: 104 additions & 0 deletions drizzle/0000_wonderful_warpath.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
CREATE TABLE `account` (
`id` text PRIMARY KEY NOT NULL,
`account_id` text NOT NULL,
`provider_id` text NOT NULL,
`user_id` text NOT NULL,
`access_token` text,
`refresh_token` text,
`id_token` text,
`access_token_expires_at` integer,
`refresh_token_expires_at` integer,
`scope` text,
`password` text,
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
`updated_at` integer NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE INDEX `account_userId_idx` ON `account` (`user_id`);--> statement-breakpoint
CREATE TABLE `session` (
`id` text PRIMARY KEY NOT NULL,
`expires_at` integer NOT NULL,
`token` text NOT NULL,
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
`updated_at` integer NOT NULL,
`ip_address` text,
`user_agent` text,
`user_id` text NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `session_token_unique` ON `session` (`token`);--> statement-breakpoint
CREATE INDEX `session_userId_idx` ON `session` (`user_id`);--> statement-breakpoint
CREATE TABLE `tournament_result` (
`id` text PRIMARY KEY NOT NULL,
`game_id` text NOT NULL,
`winner_id` text NOT NULL,
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX `tournament_result_game_id_unique` ON `tournament_result` (`game_id`);--> statement-breakpoint
CREATE INDEX `tournament_result_gameId_idx` ON `tournament_result` (`game_id`);--> statement-breakpoint
CREATE TABLE `user` (
`id` text PRIMARY KEY NOT NULL,
`name` text NOT NULL,
`email` text NOT NULL,
`email_verified` integer DEFAULT false NOT NULL,
`image` text,
`username` text,
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX `user_email_unique` ON `user` (`email`);--> statement-breakpoint
CREATE UNIQUE INDEX `user_username_unique` ON `user` (`username`);--> statement-breakpoint
CREATE TABLE `user_bracket_status` (
`id` text PRIMARY KEY NOT NULL,
`user_id` text NOT NULL,
`is_locked` integer DEFAULT false NOT NULL,
`locked_at` integer,
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `user_bracket_status_user_id_unique` ON `user_bracket_status` (`user_id`);--> statement-breakpoint
CREATE INDEX `user_bracket_status_userId_idx` ON `user_bracket_status` (`user_id`);--> statement-breakpoint
CREATE TABLE `user_prediction` (
`id` text PRIMARY KEY NOT NULL,
`user_id` text NOT NULL,
`game_id` text NOT NULL,
`predicted_winner_id` text NOT NULL,
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE INDEX `user_prediction_userId_idx` ON `user_prediction` (`user_id`);--> statement-breakpoint
CREATE INDEX `user_prediction_userId_gameId_idx` ON `user_prediction` (`user_id`,`game_id`);--> statement-breakpoint
CREATE TABLE `user_score` (
`id` text PRIMARY KEY NOT NULL,
`user_id` text NOT NULL,
`round1_score` integer DEFAULT 0 NOT NULL,
`round2_score` integer DEFAULT 0 NOT NULL,
`round3_score` integer DEFAULT 0 NOT NULL,
`round4_score` integer DEFAULT 0 NOT NULL,
`total_score` integer DEFAULT 0 NOT NULL,
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `user_score_user_id_unique` ON `user_score` (`user_id`);--> statement-breakpoint
CREATE INDEX `user_score_userId_idx` ON `user_score` (`user_id`);--> statement-breakpoint
CREATE INDEX `user_score_totalScore_idx` ON `user_score` (`total_score`);--> statement-breakpoint
CREATE TABLE `verification` (
`id` text PRIMARY KEY NOT NULL,
`identifier` text NOT NULL,
`value` text NOT NULL,
`expires_at` integer NOT NULL,
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
`updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL
);
--> statement-breakpoint
CREATE INDEX `verification_identifier_idx` ON `verification` (`identifier`);
Loading