A real-time collaborative pixel canvas where multiple users can paint together. Think Reddit's r/place, but simplified and built with modern web technologies.
- Real-time Collaboration - See other users paint instantly via Supabase Realtime
- Ghost Cursors - See where other users are hovering on the canvas
- 5-Second Cooldown - Prevents spam with a visual countdown timer
- Optimistic UI - Instant feedback on paint actions
- Completion Detection - Detects when all 400 pixels are painted
- Auto-Save to Gallery - 60-second countdown, then saves and resets
- Duplicate Prevention - Only one save per canvas (multi-user safe)
- Custom Color Picker - Click "+" to pick any color
- Heatmap Mode - Visualize which pixels are "hotly contested"
- Theme Toggle - Switch between dark and light modes
- PNG/SVG Export - Save the current canvas in either format
- Gallery Panel - Browse saved canvases in slide-out panel
- Image Viewer - Click any gallery item to view full-size
- Download History - Download old canvases as PNG or SVG
| Technology | Purpose |
|---|---|
| Nuxt 4 | Vue 3 framework with SSR |
| Supabase | Database, Realtime, Auth |
| Tailwind CSS | Utility-first styling |
| TypeScript | Type safety |
- Node.js 22.12.0 or higher
- A Supabase account (free tier works)
git clone <your-repo>
cd pixel-supa
npm install-
Create a new project at supabase.com
-
Go to Settings → API and copy your:
- Project URL
anonpublic key
-
Create
.envfile:
cp .env.example .env- Add your credentials to
.env:
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_KEY=your-anon-public-keyIn your Supabase dashboard, go to SQL Editor and run:
-- Run both migrations in order:
-- 1. supabase/migrations/001_initial_schema.sql
-- 2. supabase/migrations/002_add_gallery.sqlnpm run devOpen http://localhost:3000 🎉
pixel-supa/
├── app/
│ ├── assets/css/main.css # Theme variables + styles
│ ├── components/
│ │ ├── canvas/
│ │ │ ├── PixelCell.vue # Individual pixel
│ │ │ └── PixelGrid.vue # 20×20 grid + ghost cursors
│ │ └── ui/
│ │ ├── ColorPicker.vue # Color palette + custom picker
│ │ ├── CooldownTimer.vue # SVG countdown
│ │ ├── ExportButton.vue # PNG/SVG export dropdown
│ │ ├── GalleryPanel.vue # Gallery + viewer modal
│ │ ├── ResetBanner.vue # Countdown banner
│ │ ├── ThemeToggle.vue # Dark/light switch
│ │ └── UserStats.vue # Online count + heatmap
│ ├── composables/
│ │ ├── usePixelCanvas.ts # Supabase data + realtime
│ │ ├── useCooldown.ts # Paint timer logic
│ │ ├── usePresence.ts # Ghost cursor tracking
│ │ ├── useTheme.ts # Theme management
│ │ └── useCanvasReset.ts # Canvas lifecycle + gallery
│ └── pages/
│ └── index.vue # Main app page
├── supabase/
│ └── migrations/
│ ├── 001_initial_schema.sql
│ └── 002_add_gallery.sql
├── nuxt.config.ts
└── package.json
| Column | Type | Description |
|---|---|---|
id |
SERIAL |
Primary key |
x |
INT |
X coordinate (0-19) |
y |
INT |
Y coordinate (0-19) |
color |
TEXT |
Hex color code |
change_count |
INT |
Times painted (for heatmap) |
| Column | Type | Description |
|---|---|---|
id |
SERIAL |
Primary key |
snapshot |
JSONB |
Pixel colors as key-value pairs |
completed_at |
TIMESTAMPTZ |
When canvas was saved |
The app supports dark and light modes:
- Toggle via the sun/moon button in the top bar
- Preference is saved to localStorage
- All colors use CSS custom properties
Click the "🧪 Fill All" button to instantly fill all pixels (for testing the gallery save feature).
MIT License - feel free to use this project for learning or as a starting point for your own real-time apps!
Built with ❤️ using Nuxt + Supabase