This project is for Creativeland, which is an holding company and the LLC that encompasses the following:
- Creativeland (wearecreativeland.com)
- Fast Facts Live (fastfactslive.com)
- In Conclusion! (inconclusion.wearecreativeland.com)
- Tom Hillmeyer (tomhillmeyer.com)
- Creativeland Apps (apps.wearecreativeland.com)

Each of these sites is going to be deployed to their own Firebase projects, where their pages will be hosted. They are individual React + Vite websites/web apps.

They are nested under a parent Creativeland folder, as there are certain elements I want to share across all sites (nav bar, footer, certain legal pages, etc). 

Right now the focus is on Creativeland Apps, which include the following:
- Companion Dashboard
- Capacitimer
- Media Control Bridge

This is the very beginning of the app. I want to build the very basic Creativeland landing page for wearecreativeland.com with a basic nav bar and footer that is shared across all apps.

After the basic landing page for Creativeland is made, let's build out apps.

apps.wearecreativeland.com - leads to a login page and a page to handle licensing.
apps.wearecreativeland.com/dashboard - leads to a landing page for Companion Dashboard with its help pages and full documentation
apps.wearecreativeland.com/capacitimer - leads to a landing page for Capacitimer with its help pages and full documentation
apps.wearecreativeland.com/mcb - leads to a landing page for Media Control Bridge with its help pages and full documentation

Creativeland, Fast Facts Live, In Conclusion!, Tom Hillmeyer, and Apps are individual React + Vite projects.

Color Scheme:
- Primary: Black (#000) on White background
- Accent: #74D1F3 (powder blue)
- Secondary: #18294A (navy) for cards/sections

## Project Structure

**Monorepo Layout:**
```
creativeland/
├── shared/
│   ├── components/ (NavBar.tsx, Footer.tsx with matching .css)
│   ├── assets/ (logos, favicon)
│   └── package.json (React + @types/react for TypeScript compilation)
├── creativeland/ (wearecreativeland.com - main landing page)
├── apps/ (apps.wearecreativeland.com - authentication + app portal)
├── fastfactslive/ (fastfactslive.com)
├── inconclusion/ (inconclusion.wearecreativeland.com)
└── tomhillmeyer/ (tomhillmeyer.com)
```

**Shared Components** (shared/components/):
- NavBar and Footer are imported via relative paths (e.g., `../../../shared/components/NavBar`)
- Logo assets (white wordmark for navbar) and favicon stored in shared/assets/
- All sites use the same nav/footer for consistency
- Favicon copied to each project's public folder during setup

**Color Scheme:**
- Black (#000) background
- White text
- #74D1F3 (powder blue) for accents and interactive elements
- #18294A (navy) for card backgrounds and section breaks

**Deployment:**
- Each site is a separate Vite + React project with its own Firebase hosting configuration
- Build command: `npm run build` (outputs to dist/)
- Firebase config: firebase.json + .firebaserc in each project directory
- Apps project configured for SPA routing (all routes redirect to /index.html)

## Step Progress

### 1. ✅ Creativeland Main Landing Page
Landing page structure: hero section → about section → portfolio grid with cards for each property (Apps, Fast Facts Live, In Conclusion!, Tom Hillmeyer). All sections use flexbox for responsive design.

### 2. ✅ Apps Authentication & User Management

**Authentication Flow:**
- Firebase Auth handles all auth logic (apps/src/firebase.ts exports auth, db, analytics)
- AuthContext (apps/src/contexts/AuthContext.tsx) provides hooks: useAuth() returns currentUser, all auth methods
- Email/password signup requires name field which sets Firebase Auth displayName + Firestore document
- GitHub OAuth uses getAdditionalUserInfo() to extract username (fallback: email prefix)
  - **GitHub Email Handling:** Since GitHub may not provide email (privacy settings), after OAuth completes:
    - Login page checks if `auth.currentUser.email` exists
    - If email exists: user proceeds directly to /apps (existing users won't see prompt)
    - If email is null: shows "One More Thing" email prompt (new signups only)
    - User must provide email before accessing /apps - email is stored via addEmailToProfile() which updates Firestore
  - createUserProfile() accepts optional emailOverride parameter to store manually collected emails
- Google OAuth extracts displayName from OAuth profile and email is always provided

**User Data:**
- Firestore users collection: {email, displayName, createdAt, purchases:[]}
- Email can be null initially for GitHub users, but is collected via post-OAuth prompt
- Firestore rules (apps/firestore.rules): users can only read/write their own docs, purchases read-only
- User profile created automatically on first signup/OAuth with createUserProfile() helper

**Routes:**
- / = Login (public)
- /apps = Main user portal (protected) - formerly /licensing, renamed since apps may have different monetization models
- /account = Account settings page (protected)
- /dashboard, /capacitimer, /mcb = App landing pages (public)

**Account Settings (/account):**
Full account management UI includes:
- Profile: Change display name, change email (email/password users only)
- Security: Change password (email/password users only)
- Connected Accounts: Link/unlink Google, GitHub, or email/password providers
  - Users must have at least 1 auth method (unlinking prevented if last method)
  - Uses Firebase linkWithPopup(), linkWithCredential(), unlink()
  - Provider icons use react-icons library (FaGoogle, FaGithub, FaEnvelope)

**Apps Page (/apps):**
Main portal showing welcome message with displayName + email, Account Settings button, Sign Out button, and app cards for purchasing/accessing apps

### 3. ✅ Stripe Integration for Purchases

**Overview:**
Apps are available for one-time purchase via Stripe Checkout. The flow uses Firebase Functions v2 (Cloud Run) for secure server-side operations.

**Pricing:**
- Capacitimer: $14.99 (one-time purchase)
- Media Control Bridge: $4.99 (one-time purchase)
- Companion Dashboard: TBD

**Purchase Flow:**
1. User clicks "Purchase" on app card in /apps
2. Frontend calls Firebase Function `createCheckoutSession` directly (Cloud Run URL)
3. Function creates Stripe Checkout session (mode: 'payment' for one-time) and returns sessionUrl
4. User redirected to Stripe Checkout (hosted payment page)
5. After payment, Stripe webhook calls `stripeWebhook` Firebase Function
6. Function creates purchase record in Firestore purchases collection
7. Function updates user's purchases array in users collection
8. User sees purchased app in "Your Purchased Apps" section

**Frontend (apps/src/pages/Apps.tsx):**
- Fetches user's purchases from Firestore on load
- Separates apps into two sections: "Your Purchased Apps" (purchased) and "All Apps" (available to purchase)
- `handlePurchase()` calls createCheckoutSession endpoint directly (no path appending) and redirects to Stripe
- Apps with `stripePriceId` show "Purchase" button with price, others show "Coming Soon" or "TBD"
- Uses VITE_FIREBASE_FUNCTIONS_URL env var pointing to Cloud Run URL

**Backend (Firebase Functions v2):**
Located in apps/functions/src/index.ts using Firebase Functions v2 (Gen 2):

- **`createCheckoutSession`** - Creates Stripe checkout session (payment mode for one-time purchases)
  - Deployed URL: https://createcheckoutsession-piqesuaqxa-uc.a.run.app
  - Uses lazy Stripe initialization: `getStripe()` function to access secrets at runtime
  - Includes CORS headers for Firebase Hosting domain
  - Accepts: {priceId, userId, userEmail, productId}
  - Returns: {sessionUrl}

- **`stripeWebhook`** - Handles Stripe events:
  - Deployed URL: https://stripewebhook-piqesuaqxa-uc.a.run.app
  - `checkout.session.completed` - Creates purchase record, updates user's purchases array
  - `customer.subscription.updated/deleted` - Legacy handlers (not used for one-time purchases)
  - Verifies webhook signature using STRIPE_WEBHOOK_SECRET

**Firebase Functions v2 Setup:**
- Uses Secret Manager instead of Runtime Config (functions.config() is deprecated)
- Secrets defined with `defineSecret()` from 'firebase-functions/params'
- Functions declare secrets in options: `{secrets: [secretName]}`
- Secrets accessed via `process.env.STRIPE_SECRET_KEY` at runtime
- Stripe initialized lazily in `getStripe()` function to avoid module-load-time initialization
- Commands:
  - Set secret: `echo "value" | firebase functions:secrets:set SECRET_NAME`
  - Deploy: `firebase deploy --only functions`

**Firestore Structure:**
- `purchases` collection: {userId, productId, stripeSessionId, stripeCustomerId, stripePriceId, stripePaymentIntentId, status, purchaseDate}
- `users.purchases` array: [{productId, purchaseDate, status}]

**Security:**
- Stripe Secret Key stored in Firebase Secret Manager (STRIPE_SECRET_KEY)
- Stripe Webhook Secret stored in Firebase Secret Manager (STRIPE_WEBHOOK_SECRET)
- Stripe Publishable Key in .env file (safe for frontend: VITE_STRIPE_PUBLISHABLE_KEY)
- Webhook signature verification prevents unauthorized requests
- Firestore rules: users can only read their own purchases, only server can create/update

**Environment Variables:**
apps/.env:
```env
VITE_STRIPE_PUBLISHABLE_KEY=pk_live_...
VITE_FIREBASE_FUNCTIONS_URL=https://createcheckoutsession-piqesuaqxa-uc.a.run.app
```

**Stripe Webhook Configuration:**
- Webhook URL: https://stripewebhook-piqesuaqxa-uc.a.run.app
- Events: checkout.session.completed, customer.subscription.created/updated/deleted
- Webhook secret stored in Firebase Secret Manager

**Custom Domain:**
- apps.wearecreativeland.com configured in Firebase Hosting
- Added to Firebase Auth authorized domains to allow authentication from custom domain

**Implementation Notes:**
- Firebase Functions v2 URLs are direct Cloud Run URLs (not /functionName paths)
- Frontend calls function URL directly without appending paths
- Stripe API version: '2026-01-28.clover'
- One-time payment mode (not subscription) for all app purchases

### 4. ✅ License Key Generation

**Overview:**
Each purchase generates a unique 15-character license key for completely offline verification in desktop apps. License keys use HMAC-SHA256 checksum for cryptographic verification without requiring internet access or user database.

**License Key Format:**
- **Structure:** `PRODUCTRRRRRRRRRCCC` (15 characters total, no dashes)
- **Prefix (3 chars):** Product identifier (CAP, MCB, CDB)
- **Random Body (9 chars):** Cryptographically random unique identifier
- **Checksum (3 chars):** HMAC-SHA256 checksum for offline verification
- **Example:** `CAPABC123XYZ7D8`

**Character Set:**
- Custom Base34 alphabet: `0123456789ABCDEFGHJKLMNPQRSTUVWXYZ` (34 characters)
- **Excludes I** to avoid confusion with 1
- **Excludes O** to avoid confusion with 0
- Human-readable and typeable while maintaining security

**Generation (apps/functions/src/index.ts):**
```typescript
// License key secret - MUST be embedded in desktop apps for offline verification
const LICENSE_SECRET = 'your-secret-key-change-this-in-production-8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c';

// Product prefixes
const PRODUCT_PREFIXES = {
  'capacitimer': 'CAP',
  'mcb': 'MCB',
  'companion-dashboard': 'CDB'
};

function generateLicenseKey(productId: string): string {
  // 1. Generate 9 random characters
  // 2. Compute HMAC checksum from PREFIX+RANDOM:productId
  // 3. Append 3-char checksum
  // Returns: 15-char license key with verifiable checksum
}
```

**Offline Verification:**
- Desktop apps verify licenses completely offline (no internet/database required)
- Apps recompute HMAC-SHA256 checksum using embedded secret key
- Checksum validation proves the key is authentic and valid for that product
- No user binding - keys are transferable (consider if acceptable for your use case)

**Storage:**
- License keys stored in Firestore `purchases` collection with purchase records
- Also stored in user's `purchases` array in `users` collection
- Displayed on /apps page with copy-to-clipboard button

**Frontend Display (apps/src/pages/Apps.tsx):**
- Shows license key in "Your Purchased Apps" section
- Styled with blue-accented box and monospace font
- "Copy" button for easy clipboard access
- License key included in UserPurchase interface

**Security Considerations:**
- LICENSE_SECRET must be identical in server (Firebase) and desktop apps
- Secret should be obfuscated in desktop app binaries
- Changing secret invalidates all existing licenses (coordinate carefully)
- User ID binding makes licenses non-transferable between accounts

**Documentation:**
- Complete implementation guide in LICENSE_VERIFICATION.md
- Includes verification code examples for JavaScript, Python, and C#
- Explains HMAC-SHA256 cryptographic approach
- Provides troubleshooting and testing guidance

**Test Mode:**
- Test Stripe keys configured in apps/.env for testing purchases
- Test webhook secret configured in Firebase Secret Manager
- Test price IDs used for development purchases
- Production keys to be swapped before launch