Workout Window Feature - Implementation Plan
Feature Name: Workout Window
Domain: workoutwindow.com (integrated into HiveJournal)
Status: Planning Phase
Overview
Workout Window is a gamified accountability system that helps users commit to and complete activities (workouts, meditation, reading, homework, etc.) during a 2-hour window each day. The system uses location-based matching to create chains of users, encourages team collaboration, and includes competitive elements with weekly battles.
Core Concepts
1. Workout Window
- User selects a 2-hour time window during their day
- Commits to completing an activity during that window
- Activity types: cardio, strength training, walk, meditation, reading, homework, etc.
2. Chain System
- Users are matched based on:
- Geographic proximity (IP-based location)
- Workout window timing (before/after relationships)
- Each user has a "before" and "after" person in the chain
- Chain continues as long as each person checks in successfully
- Goal: "Don't break the chain"
3. Check-in System
- Users upload a selfie or timestamped photo after completing their activity
- Photo must be taken during or shortly after their workout window
- Validates timestamp and location (optional)
- Triggers "baton pass" to next person in chain
4. Daily Mosaic
- Low-poly mosaic image generated daily
- Each section represents a participant's status:
- Success (completed check-in)
- Failure (missed check-in)
- Pending (window hasn't started yet)
- Clickable sections show participant details
- Visual representation of overall challenge progress
5. Team Structure
- 3-Person Squads: Person before, current person, person after
- Red/Blue Teams: Larger competing teams (squads assigned to one)
- Daily Competition: Teams compete for completion rates
- Weekly Battle: End-of-week showdown with accumulated battle chips
6. Battle System
- Each successful check-in earns a "battle chip"
- Battle chips power a robot for the weekly battle
- More successes = stronger robot = better chance of defeating the "bad guy"
- Weekly showdown on Sunday
Database Schema
Tables
workout_windows
CREATE TABLE workout_windows (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
activity_type VARCHAR(50) NOT NULL, -- 'cardio', 'strength', 'walk', 'meditation', 'reading', 'homework', 'other'
custom_activity VARCHAR(100), -- if 'other'
window_start_time TIME NOT NULL, -- e.g., '14:00:00' (2 PM)
window_end_time TIME NOT NULL, -- e.g., '16:00:00' (4 PM)
timezone VARCHAR(50) NOT NULL,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE(user_id) -- One active window per user
);
workout_window_chains
CREATE TABLE workout_window_chains (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
date DATE NOT NULL,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
before_user_id UUID REFERENCES users(id), -- Person whose window comes before
after_user_id UUID REFERENCES users(id), -- Person whose window comes after
team_color VARCHAR(10) NOT NULL CHECK (team_color IN ('red', 'blue')),
squad_id UUID REFERENCES workout_window_squads(id),
created_at TIMESTAMP DEFAULT NOW(),
UNIQUE(date, user_id)
);
workout_window_squads
CREATE TABLE workout_window_squads (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
date DATE NOT NULL,
user_1_id UUID NOT NULL REFERENCES users(id),
user_2_id UUID NOT NULL REFERENCES users(id),
user_3_id UUID NOT NULL REFERENCES users(id),
team_color VARCHAR(10) NOT NULL CHECK (team_color IN ('red', 'blue')),
created_at TIMESTAMP DEFAULT NOW(),
UNIQUE(date, user_1_id, user_2_id, user_3_id)
);
workout_window_checkins
CREATE TABLE workout_window_checkins (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
chain_id UUID NOT NULL REFERENCES workout_window_chains(id) ON DELETE CASCADE,
date DATE NOT NULL,
activity_type VARCHAR(50) NOT NULL,
photo_url TEXT NOT NULL, -- Supabase storage URL
photo_timestamp TIMESTAMP NOT NULL, -- EXIF timestamp from photo
location_lat DECIMAL(10, 8), -- Optional
location_lng DECIMAL(11, 8), -- Optional
status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'verified', 'rejected')),
battle_chips_earned INTEGER DEFAULT 1,
created_at TIMESTAMP DEFAULT NOW(),
UNIQUE(user_id, date)
);
workout_window_locations
CREATE TABLE workout_window_locations (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
ip_address INET,
latitude DECIMAL(10, 8),
longitude DECIMAL(11, 8),
city VARCHAR(100),
region VARCHAR(100),
country VARCHAR(2),
timezone VARCHAR(50),
last_updated TIMESTAMP DEFAULT NOW(),
UNIQUE(user_id)
);
workout_window_battles
CREATE TABLE workout_window_battles (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
week_start_date DATE NOT NULL, -- Monday of the week
week_end_date DATE NOT NULL, -- Sunday of the week
red_team_chips INTEGER DEFAULT 0,
blue_team_chips INTEGER DEFAULT 0,
red_team_robot_power INTEGER DEFAULT 0,
blue_team_robot_power INTEGER DEFAULT 0,
battle_status VARCHAR(20) DEFAULT 'pending' CHECK (battle_status IN ('pending', 'in_progress', 'completed')),
winner VARCHAR(10) CHECK (winner IN ('red', 'blue', 'tie')),
battle_result JSONB, -- Detailed battle outcome
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE(week_start_date)
);
workout_window_mosaics
CREATE TABLE workout_window_mosaics (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
date DATE NOT NULL,
mosaic_image_url TEXT NOT NULL, -- Generated low-poly mosaic
mosaic_data JSONB NOT NULL, -- Array of sections with user_id, status, coordinates
total_participants INTEGER NOT NULL,
completed_count INTEGER DEFAULT 0,
failed_count INTEGER DEFAULT 0,
pending_count INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT NOW(),
UNIQUE(date)
);
API Endpoints
User Management
POST /api/workout-window/setup
Create or update user's workout window
Request: {
activityType: 'cardio' | 'strength' | 'walk' | 'meditation' | 'reading' | 'homework' | 'other',
customActivity?: string,
windowStartTime: string, // "14:00"
windowEndTime: string, // "16:00"
timezone: string
}
Response: {
window: WorkoutWindow,
message: string
}
GET /api/workout-window/my-window
Get current user's workout window
Response: {
window: WorkoutWindow | null,
location: UserLocation | null
}
PUT /api/workout-window/update-location
Update user's location (called periodically)
Request: {
ipAddress?: string,
latitude?: number,
longitude?: number,
city?: string,
region?: string,
country?: string,
timezone?: string
}
Chain & Team Management
GET /api/workout-window/chain/:date
Get user's chain for a specific date
Response: {
chain: {
beforeUser: User | null,
currentUser: User,
afterUser: User | null,
teamColor: 'red' | 'blue',
squad: Squad
},
checkins: CheckIn[]
}
GET /api/workout-window/squad/:date
Get user's 3-person squad for a date
Response: {
squad: {
user1: User,
user2: User,
user3: User,
teamColor: 'red' | 'blue'
},
checkins: CheckIn[]
}
GET /api/workout-window/team-stats/:date
Get team statistics for a date
Response: {
redTeam: {
totalMembers: number,
completed: number,
failed: number,
pending: number,
completionRate: number,
totalChips: number
},
blueTeam: {
totalMembers: number,
completed: number,
failed: number,
pending: number,
completionRate: number,
totalChips: number
}
}
Check-in System
POST /api/workout-window/checkin
Submit a check-in with photo
Request: FormData {
photo: File,
activityType: string,
latitude?: number,
longitude?: number
}
Response: {
checkin: CheckIn,
battleChipsEarned: number,
chainStatus: 'continued' | 'broken',
nextUserNotified: boolean
}
GET /api/workout-window/checkin/:date
Get check-in status for a date
Response: {
checkin: CheckIn | null,
status: 'pending' | 'completed' | 'failed' | 'not_started'
}
GET /api/workout-window/checkins/:date
Get all check-ins for a date (for team view)
Response: {
checkins: CheckIn[],
stats: {
total: number,
completed: number,
failed: number
}
}
Mosaic System
GET /api/workout-window/mosaic/:date
Get daily mosaic
Response: {
mosaic: {
imageUrl: string,
date: string,
sections: Array<{
userId: string,
userName: string,
status: 'completed' | 'failed' | 'pending',
coordinates: { x: number, y: number, width: number, height: number },
teamColor: 'red' | 'blue'
}>,
stats: {
totalParticipants: number,
completed: number,
failed: number,
pending: number
}
}
}
POST /api/workout-window/mosaic/generate/:date
Generate/regenerate mosaic for a date (admin/cron)
Response: {
mosaic: Mosaic,
message: string
}
Battle System
GET /api/workout-window/battle/week/:weekStartDate
Get weekly battle data
Response: {
battle: Battle,
redTeam: {
totalChips: number,
robotPower: number,
members: number
},
blueTeam: {
totalChips: number,
robotPower: number,
members: number
},
dailyStats: Array<{
date: string,
redTeamChips: number,
blueTeamChips: number
}>
}
POST /api/workout-window/battle/execute/:weekStartDate
Execute weekly battle (admin/cron, runs Sunday evening)
Response: {
battle: Battle,
winner: 'red' | 'blue' | 'tie',
battleResult: {
redRobotPower: number,
blueRobotPower: number,
badGuyPower: number,
rounds: Array<{
round: number,
redDamage: number,
blueDamage: number,
badGuyDamage: number
}>,
finalOutcome: string
}
}
GET /api/workout-window/battle/history
Get battle history
Response: {
battles: Battle[],
userStats: {
totalChipsContributed: number,
battlesWon: number,
longestChain: number
}
}
Algorithm: Chain Matching
Step 1: Location-Based Grouping
- Get all active users with workout windows
- Group by approximate location (city/region)
- Within each location group, sort by workout window start time
Step 2: Chain Creation
- For each user, find:
- Before user: Person whose window ends closest to (but before) current user's start
- After user: Person whose window starts closest to (but after) current user's end
- Create bidirectional relationships
- Handle edge cases (first/last in chain)
Step 3: Team Assignment
- Assign users to Red/Blue teams (50/50 split, balanced by location)
- Ensure 3-person squads are on the same team
- Balance teams by total members
Step 4: Daily Chain Refresh
- Run at midnight (or early morning) for each timezone
- Recalculate chains based on:
- Active users
- Current workout windows
- Recent location updates
Frontend Components (Web)
Pages
/workout-window - Main Dashboard
- Current workout window status
- Today's chain (before/after users)
- Team stats
- Check-in button
- Battle progress
/workout-window/setup - Setup Page
- Activity type selector
- Time window picker (2-hour range)
- Timezone selection
- Location permission request
- Save button
/workout-window/chain - Chain View
- Visual chain representation
- Before/after user cards
- Check-in status for each
- Chat/encouragement messages
/workout-window/mosaic - Daily Mosaic
- Interactive mosaic image
- Click sections to see user details
- Filter by team (red/blue)
- Stats overlay
/workout-window/battle - Battle View
- Weekly battle progress
- Team robot power levels
- Battle history
- Leaderboards
Components
WorkoutWindowSetup.tsx
- Form for setting up workout window
- Time picker component
- Activity type selector
WorkoutWindowDashboard.tsx
- Main dashboard view
- Current status
- Quick actions
ChainView.tsx
- Visual chain representation
- User cards for before/after
- Check-in status indicators
CheckInModal.tsx
- Photo upload
- Activity confirmation
- Location capture (optional)
- Submit button
MosaicView.tsx
- Interactive mosaic image
- Click handlers for sections
- User detail modals
- Team filters
BattleView.tsx
- Weekly battle visualization
- Robot power meters
- Battle animation
- Results display
TeamStats.tsx
- Team comparison
- Progress bars
- Member lists
Mobile App Components
Screens
WorkoutWindowSetupScreen.tsx
- Similar to web setup
- Native time picker
- Camera permission request
WorkoutWindowDashboardScreen.tsx
- Mobile-optimized dashboard
- Swipeable cards
- Quick check-in button
CheckInScreen.tsx
- Camera integration
- Photo capture/selection
- Activity confirmation
- Location capture
- Upload progress
ChainScreen.tsx
- Vertical chain view
- Swipeable user cards
- Push notifications for baton pass
MosaicScreen.tsx
- Full-screen mosaic
- Tap to view details
- Pinch to zoom
- Team filter toggle
BattleScreen.tsx
- Battle visualization
- Animated robot battle
- Team stats
- History view
Native Features
- Camera Integration: Use
expo-image-pickerfor photo capture - Location Services: Use
expo-locationfor GPS coordinates - Push Notifications: Notify when it's user's turn or baton is passed
- Background Tasks: Check for check-in reminders
- Photo Metadata: Extract EXIF timestamp from photos
Image Generation: Low-Poly Mosaic
Approach
-
Base Image Selection:
- Use a motivational/fitness-themed image
- Or generate procedurally
- Or user-submitted themes
-
Low-Poly Generation:
- Use library like
lowpolyordelaunator - Create triangular/polygonal mesh
- Number of polygons = number of participants
- Use library like
-
Status Mapping:
- Each polygon represents one participant
- Color based on status:
- Green: Completed
- Red: Failed
- Gray: Pending
- Opacity based on team (red/blue tint)
-
Generation Process:
- Run daily after all windows have passed
- Use server-side image processing (Node.js + Canvas/Sharp)
- Store in Supabase storage
- Cache for quick access
Implementation
// Pseudo-code for mosaic generation
async function generateMosaic(date: Date) {
const participants = await getParticipantsForDate(date)
const baseImage = await loadBaseImage()
const polygons = generateLowPolyMesh(baseImage, participants.length)
const sections = participants.map((user, index) => {
const polygon = polygons[index]
const status = getCheckInStatus(user, date)
return {
userId: user.id,
coordinates: polygon.vertices,
status: status,
color: getStatusColor(status, user.teamColor)
}
})
const mosaicImage = await renderMosaic(baseImage, sections)
await saveMosaic(mosaicImage, date, sections)
}
Battle System Mechanics
Battle Chip Calculation
function calculateBattleChips(checkin: CheckIn): number {
let chips = 1 // Base chip
// Bonus for chain continuation
if (checkin.chainContinued) {
chips += 1
}
// Bonus for early check-in (within first 30 min of window)
if (checkin.isEarly) {
chips += 0.5
}
// Bonus for consecutive days
if (checkin.consecutiveDays >= 7) {
chips += 2
}
return Math.floor(chips)
}
Robot Power Calculation
function calculateRobotPower(teamChips: number, teamMembers: number): number {
const basePower = teamChips * 10
const memberBonus = teamMembers * 5
const efficiencyBonus = (teamChips / teamMembers) * 20
return Math.floor(basePower + memberBonus + efficiencyBonus)
}
Battle Execution
- Bad Guy Power: Fixed or calculated based on total participants
- Rounds: 3-5 rounds of combat
- Damage Calculation:
- Each team's robot attacks bad guy
- Bad guy attacks both teams
- Power determines damage dealt
- Victory Conditions:
- Team that deals most damage wins
- If bad guy defeats both teams, it's a loss
- If teams tie, it's a tie
Background Jobs / Cron Tasks
Daily Tasks
-
Chain Matching (00:00 UTC)
- Recalculate chains for all users
- Assign teams
- Create 3-person squads
- Send notifications
-
Check-in Reminders (30 min before window)
- Send push notification
- Email reminder (optional)
-
Mosaic Generation (23:59 UTC)
- Generate mosaic for the day
- Store in database
- Clear cache
-
Failed Check-in Processing (End of day)
- Mark missed check-ins as failed
- Update chain status
- Calculate battle chips
Weekly Tasks
-
Battle Execution (Sunday 23:00 UTC)
- Calculate final team powers
- Execute battle
- Determine winner
- Send results notifications
- Reset for next week
-
Weekly Stats (Monday 00:00 UTC)
- Calculate weekly leaderboards
- Award achievements
- Reset battle data
Notifications
Push Notifications
- Baton Pass: "It's your turn! [User] just completed their workout."
- Check-in Reminder: "Your workout window starts in 30 minutes!"
- Chain Broken: "The chain was broken. Let's get it back on track!"
- Battle Update: "Your team is winning! Keep it up!"
- Battle Results: "Battle complete! [Team] won this week!"
In-App Notifications
- Real-time chain updates
- Team chat messages
- Battle progress updates
Security & Validation
Photo Validation
-
Timestamp Verification:
- Extract EXIF timestamp
- Verify it's within workout window (with grace period)
- Reject if too early/late
-
Location Verification (Optional):
- Compare photo location with user's registered location
- Flag suspicious check-ins for review
-
Photo Analysis:
- Basic validation (not blank, actual photo)
- Optional: AI verification (person in photo, workout context)
Anti-Cheat Measures
- Rate Limiting: One check-in per day per user
- Time Windows: Strict enforcement of workout window times
- Location Tracking: Monitor for suspicious patterns
- Manual Review: Flag unusual check-ins for admin review
Implementation Phases
Phase 1: Core Setup (Week 1-2)
- Database schema creation
- Basic API endpoints (setup, check-in)
- Web setup page
- Mobile setup screen
- Location tracking
Phase 2: Chain System (Week 3-4)
- Chain matching algorithm
- Team assignment
- Chain view (web & mobile)
- Baton pass notifications
Phase 3: Check-in System (Week 5-6)
- Photo upload & validation
- Check-in API
- Check-in UI (web & mobile)
- Status tracking
Phase 4: Mosaic (Week 7-8)
- Low-poly image generation
- Mosaic rendering
- Interactive mosaic view
- Daily generation job
Phase 5: Battle System (Week 9-10)
- Battle chip calculation
- Robot power system
- Battle execution
- Battle visualization
- Weekly battle job
Phase 6: Polish & Launch (Week 11-12)
- Notifications system
- Analytics & tracking
- Performance optimization
- Testing & bug fixes
- Documentation
- Launch preparation
Technical Stack
Backend
- Database: PostgreSQL (Supabase)
- API: Express.js (existing backend)
- Image Processing: Sharp or Canvas (Node.js)
- Low-Poly Generation: Custom algorithm or library
- Location Services: IP geolocation API (MaxMind, ipapi.co)
- Cron Jobs: Node-cron or Vercel Cron
Frontend (Web)
- Framework: Next.js (existing)
- UI Components: Existing component library
- Image Display: Next.js Image component
- Real-time: WebSockets or Server-Sent Events
Mobile
- Framework: React Native (Expo)
- Camera: expo-image-picker
- Location: expo-location
- Notifications: expo-notifications
- Image Display: react-native-fast-image
Image Processing
- Low-Poly: Custom implementation or library
- Mosaic Generation: Canvas API or Sharp
- Storage: Supabase Storage
Open Questions / Decisions Needed
-
Domain Integration:
- Should workoutwindow.com redirect to hivejournal.com/workout-window?
- Or maintain separate subdomain?
- Branding: "HiveJournal Workout Window" or separate brand?
-
Activity Types:
- Fixed list or user-defined?
- Categories or free-form?
-
Team Names:
- Red/Blue or more creative names?
- User-voted team names?
-
Battle Theme:
- Robot vs bad guy theme?
- Alternative themes (sports, fantasy, etc.)?
- User-customizable?
-
Mosaic Base Images:
- Fixed images or rotating themes?
- User-submitted themes?
- AI-generated?
-
Location Privacy:
- How precise should location be?
- City-level or more granular?
- Opt-in for precise location?
-
Chain Size:
- Maximum chain length?
- What if no matches found?
-
Battle Frequency:
- Weekly only or daily mini-battles?
- Monthly grand battles?
-
Monetization:
- Free feature or premium?
- In-app purchases for battle chips?
- Sponsorships/partnerships?
-
Gamification:
- Additional achievements/badges?
- Leaderboards?
- Streak rewards?
Success Metrics
Engagement Metrics
- Daily active users
- Check-in completion rate
- Average chain length
- Team participation rates
Retention Metrics
- Weekly retention rate
- Monthly retention rate
- Feature adoption rate
Social Metrics
- Messages sent between chain members
- Team chat activity
- Battle participation rate
Business Metrics
- User acquisition from workoutwindow.com
- Conversion to paid users (if applicable)
- Feature satisfaction scores
Next Steps
- Review & Approve Plan
- Answer Open Questions
- Create Detailed Technical Specs
- Set Up Development Environment
- Begin Phase 1 Implementation
Resources & References
- Low-poly image generation: https://github.com/zz85/lowpoly
- IP geolocation: https://ipapi.co/
- Photo EXIF extraction: https://github.com/exif-js/exif-js
- Battle system inspiration: Various mobile games with team battles
Document Version: 1.0
Last Updated: December 2024
Status: Planning Phase