A private festival website with password protection, attendee management, timeline/schedule, and voting system for the Capawards.
- Password Protection: Single shared password gate with 7-day cookie session
- Festival Timeline: Complete schedule with day-by-day events
- Attendee Directory: Flip cards showing room assignments, previous editions, and groups
- Capawards Voting: Multi-step wizard for voting with no self-voting enforcement
- Admin Dashboard: Control voting window and view real-time results
- La Mafia Game: Interactive explanation of the festival game
- Responsive Design: Mobile-first with 80s/90s festival aesthetics
- Framework: Next.js 14 (App Router) + TypeScript
- Styling: Tailwind CSS with custom 80s/90s theme
- Animations: Framer Motion
- Database: Supabase (PostgreSQL)
- Deployment: Vercel
- Node.js 18+ and npm
- Supabase account (free tier works)
-
Clone the repository
git clone <repository-url> cd capafest-offline
-
Install dependencies
npm install
-
Set up Supabase
Create a new project on Supabase and run the following SQL:
-- Attendees table CREATE TABLE attendees ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name TEXT NOT NULL, photo_url TEXT, room TEXT NOT NULL, previous_editions TEXT[] DEFAULT '{}', group_names TEXT[] DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Timeline events CREATE TABLE timeline_events ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), day TEXT NOT NULL CHECK (day IN ('viernes', 'sabado', 'domingo')), "order" INTEGER NOT NULL, icon TEXT NOT NULL, title TEXT NOT NULL, description TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Capawards categories CREATE TABLE capawards_categories ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name TEXT NOT NULL, "order" INTEGER NOT NULL, dj_only BOOLEAN DEFAULT FALSE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Votes CREATE TABLE votes ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), voter_id UUID REFERENCES attendees(id) NOT NULL, category_id UUID REFERENCES capawards_categories(id) NOT NULL, nominee_id UUID REFERENCES attendees(id) NOT NULL, edition TEXT NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(voter_id, category_id, edition) ); -- Settings CREATE TABLE settings ( key TEXT PRIMARY KEY, value JSONB NOT NULL, updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Insert default voting setting INSERT INTO settings (key, value) VALUES ('voting_open', '{"enabled": false}'); -- Indexes for performance CREATE INDEX idx_votes_voter ON votes(voter_id); CREATE INDEX idx_votes_edition ON votes(edition); CREATE INDEX idx_votes_category ON votes(category_id);
-
Configure environment variables
Copy
.env.local.exampleto.env.local:cp .env.local.example .env.local
Fill in your Supabase credentials:
NEXT_PUBLIC_SUPABASE_URL=your-project-url.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key PASSWORD_HASH=capafest_offline26 ADMIN_SECRET=capafest_admin_secret
-
Seed the database
Use the Supabase dashboard or SQL editor to insert sample data:
-- Example attendee INSERT INTO attendees (name, photo_url, room, previous_editions, group_names) VALUES ('DJ Polita', '/avatars/polita.jpg', 'HabitaciΓ³n 1', ARRAY['aliens', 'love', 'awaked', 'human', 'hippie', 'metro', 'inferno'], ARRAY['DJ', 'Founding']); -- Add more attendees following the pattern in src/lib/seedData.ts -- Insert categories INSERT INTO capawards_categories (name, "order", dj_only) VALUES ('Caposo (mejor vestido)', 1, false), ('CapΓn (mΓ‘s gracioso)', 2, false), ('Caporro (mΓ‘s loco)', 3, false), ('CapacaΓ±Γ³n (mejor bailarΓn)', 4, false), ('Bizacap (mejor sesiΓ³n de mΓΊsica)', 5, true), ('Capamante (mΓ‘s cariΓ±oso)', 6, false);
-
Run the development server
npm run dev
Open http://localhost:3000/acceso in your browser.
- Password:
capafest_offline26 - Admin secret (for admin API calls):
capafest_admin_secret
src/
βββ app/
β βββ acceso/ # Password gate page
β βββ cartel/ # Timeline/schedule
β βββ la-people/ # Attendees directory
β βββ capawards/ # Voting wizard
β βββ mafia/ # Game rules
β βββ admin/ # Admin dashboard
β βββ api/
β βββ verify-password/ # Password check
β βββ vote/ # Vote submission
β βββ admin/ # Admin endpoints
βββ components/
β βββ Navigation.tsx # Sticky nav
β βββ Footer.tsx
β βββ AttendeeCard.tsx # Flip card
βββ lib/
β βββ supabase.ts # DB client
β βββ utils.ts
β βββ seedData.ts # Sample data
βββ types/
βββ index.ts # TypeScript types
Update src/lib/seedData.ts or insert directly into Supabase.
Edit tailwind.config.js to modify the color palette.
Place avatar images in public/avatars/ and update photo URLs in the database.
Add room images to public/rooms/ and update the roomBackgrounds mapping in seedData.ts.
- Push your code to GitHub
- Import project in Vercel
- Add environment variables in Vercel dashboard
- Deploy!
Make sure to set these in Vercel:
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYPASSWORD_HASHADMIN_SECRET
Access /admin and use the toggle button, or call the API:
curl -X POST https://your-domain.com/api/admin/toggle-voting \
-H "Content-Type: application/json" \
-d '{"secret": "capafest_admin_secret", "enabled": true}'The admin dashboard shows live results. Export CSV for analysis.
- Implemented via Next.js middleware
- Cookie lasts 7 days
- Password never exposed to client
- Identity selection (honor system, no real auth)
- One vote per attendee per edition (DB constraint)
- No self-voting enforcement
- DJ-only category (Bizacap) filters non-DJ attendees
- Multi-step wizard with review
- CSS 3D transform
- Click/tap to flip
- Shows room + badges on back
Private festival project. All rights reserved.
Built with love for Capafest 2026 - Offline Edition π