reference

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

  1. Get all active users with workout windows
  2. Group by approximate location (city/region)
  3. Within each location group, sort by workout window start time

Step 2: Chain Creation

  1. 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
  2. Create bidirectional relationships
  3. Handle edge cases (first/last in chain)

Step 3: Team Assignment

  1. Assign users to Red/Blue teams (50/50 split, balanced by location)
  2. Ensure 3-person squads are on the same team
  3. 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-picker for photo capture
  • Location Services: Use expo-location for 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

  1. Base Image Selection:

    • Use a motivational/fitness-themed image
    • Or generate procedurally
    • Or user-submitted themes
  2. Low-Poly Generation:

    • Use library like lowpoly or delaunator
    • Create triangular/polygonal mesh
    • Number of polygons = number of participants
  3. Status Mapping:

    • Each polygon represents one participant
    • Color based on status:
      • Green: Completed
      • Red: Failed
      • Gray: Pending
    • Opacity based on team (red/blue tint)
  4. 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

  1. Bad Guy Power: Fixed or calculated based on total participants
  2. Rounds: 3-5 rounds of combat
  3. Damage Calculation:
    • Each team's robot attacks bad guy
    • Bad guy attacks both teams
    • Power determines damage dealt
  4. 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

  1. Chain Matching (00:00 UTC)

    • Recalculate chains for all users
    • Assign teams
    • Create 3-person squads
    • Send notifications
  2. Check-in Reminders (30 min before window)

    • Send push notification
    • Email reminder (optional)
  3. Mosaic Generation (23:59 UTC)

    • Generate mosaic for the day
    • Store in database
    • Clear cache
  4. Failed Check-in Processing (End of day)

    • Mark missed check-ins as failed
    • Update chain status
    • Calculate battle chips

Weekly Tasks

  1. Battle Execution (Sunday 23:00 UTC)

    • Calculate final team powers
    • Execute battle
    • Determine winner
    • Send results notifications
    • Reset for next week
  2. Weekly Stats (Monday 00:00 UTC)

    • Calculate weekly leaderboards
    • Award achievements
    • Reset battle data

Notifications

Push Notifications

  1. Baton Pass: "It's your turn! [User] just completed their workout."
  2. Check-in Reminder: "Your workout window starts in 30 minutes!"
  3. Chain Broken: "The chain was broken. Let's get it back on track!"
  4. Battle Update: "Your team is winning! Keep it up!"
  5. 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

  1. Timestamp Verification:

    • Extract EXIF timestamp
    • Verify it's within workout window (with grace period)
    • Reject if too early/late
  2. Location Verification (Optional):

    • Compare photo location with user's registered location
    • Flag suspicious check-ins for review
  3. Photo Analysis:

    • Basic validation (not blank, actual photo)
    • Optional: AI verification (person in photo, workout context)

Anti-Cheat Measures

  1. Rate Limiting: One check-in per day per user
  2. Time Windows: Strict enforcement of workout window times
  3. Location Tracking: Monitor for suspicious patterns
  4. 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

  1. Domain Integration:

    • Should workoutwindow.com redirect to hivejournal.com/workout-window?
    • Or maintain separate subdomain?
    • Branding: "HiveJournal Workout Window" or separate brand?
  2. Activity Types:

    • Fixed list or user-defined?
    • Categories or free-form?
  3. Team Names:

    • Red/Blue or more creative names?
    • User-voted team names?
  4. Battle Theme:

    • Robot vs bad guy theme?
    • Alternative themes (sports, fantasy, etc.)?
    • User-customizable?
  5. Mosaic Base Images:

    • Fixed images or rotating themes?
    • User-submitted themes?
    • AI-generated?
  6. Location Privacy:

    • How precise should location be?
    • City-level or more granular?
    • Opt-in for precise location?
  7. Chain Size:

    • Maximum chain length?
    • What if no matches found?
  8. Battle Frequency:

    • Weekly only or daily mini-battles?
    • Monthly grand battles?
  9. Monetization:

    • Free feature or premium?
    • In-app purchases for battle chips?
    • Sponsorships/partnerships?
  10. 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

  1. Review & Approve Plan
  2. Answer Open Questions
  3. Create Detailed Technical Specs
  4. Set Up Development Environment
  5. Begin Phase 1 Implementation

Resources & References


Document Version: 1.0
Last Updated: December 2024
Status: Planning Phase

WORKOUT WINDOW PLAN — Docs | HiveJournal