diff --git a/.gitignore b/.gitignore index 400f35a..8f7a3dd 100644 --- a/.gitignore +++ b/.gitignore @@ -22,5 +22,5 @@ logs .env .env.* !.env.example -/docs/ +/*.md scripts/*.js diff --git a/CLAUDE.md b/CLAUDE.md index 7cadf29..0abd815 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,94 +1,329 @@ -# CLAUDE.md +## 2. Member Features -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +### Member Profiles -## Project Overview +**Core Fields:** -Ghost Guild is a membership community platform for game developers exploring cooperative business models. Built with Nuxt 4, Vue 3, MongoDB, and Nuxt UI 4. +- Name, pronouns, time zone +- Avatar/photo - choose from ghosts +- Studio/organization affiliation +- Bio (rich text) +- Skills tags (searchable) +- Location (city/region) +- Social links (Mastodon, LinkedIn, etc.) +- **Offering:** What I can contribute +- **Looking For:** What I need support with -## Commands +**Privacy Controls:** -```bash -npm run dev # Start dev server at http://localhost:3000 -npm run build # Production build -npm run preview # Preview production build +- Public/members-only/private toggle per field +- Opt-in to member directory + +### Member Updates/Mini Blog + +- Post updates about projects, learnings, questions +- Rich text with image support +- Comments enabled +- Filter by circle or topic tags + +## 3. Events System + +### Core Features + +- RSVP with capacity limits +- Waitlist management +- Add to calendar (.ics download) +- Pre-event discussion threads +- Post-event recordings archive +- Speaker/facilitator profiles + +### Member-Proposed Events + +**Proposal Flow:** + +1. Member submits event idea via form +2. Include: Topic, format, target circle, time commitment +3. Admin quick review (spam check only) +4. Published to "Proposed Events" board +5. Members can express interest (like feature upvote pages) +6. If threshold met (e.g., 5 interested), event is scheduled +7. Proposer gets facilitator support if needed + +## 4. Resources Integration + +### Consolidating Existing Assets + +**Import and organize from:** + +- learn.weirdghosts.ca content +- Existing tools and templates +- PA curriculum materials (where appropriate) +- Case studies and examples + +**Organization Structure:** + +``` +Resources/ +├── Start Here/ +│ ├── Welcome Letter from Jennie & Eileen +│ ├── How Ghost Guild Works +│ └── Solidarity Economics Explained +├── Learning Paths/ +│ ├── Community Track → links to learn.weirdghosts.ca +│ ├── Founder Track → practical tools +│ └── Practitioner Track → advanced resources +├── Templates & Tools/ +│ ├── Governance/ +│ ├── Financial/ +│ ├── Operations/ +│ └── Legal/ +├── Case Studies/ +│ └── Member stories and examples +└── External Resources/ + └── Curated links and recommendations ``` -No test framework is currently configured. +### Resource Features -## Architecture +- Tag by circle relevance (but accessible to all) +- Download tracking for impact metrics +- Version control for templates +- Comment threads on resources +- "Request a resource" feature -### Stack +## 5. Peer Support System -- **Framework:** Nuxt 4 (Vue 3 + Nitro server) -- **UI:** Nuxt UI 4 (`@nuxt/ui@^4`) with Tailwind CSS -- **Database:** MongoDB via Mongoose -- **Auth:** JWT magic link (email-only, no passwords) -- **Payments:** Helcim (recurring subscriptions + ticket sales) -- **Email:** Resend -- **Slack:** `@slack/web-api` for member invitations and notifications -- **Images:** Cloudinary -- **Analytics:** Plausible (`ghostguild.org`) +### Cal.com Integration for 1:1s -### Key Directories +**Setup:** -- `app/composables/` — State management via `useState()` (no Pinia/Vuex). Key composables: `useAuth`, `useHelcim`, `useMemberPayment`, `useMemberStatus` -- `app/config/` — Circle definitions (`circles.js`) and contribution tiers (`contributions.js`) used across frontend and forms -- `app/middleware/` — Route guards: `auth.js` (member pages), `admin.js` (admin pages), `coming-soon.global.js` (launch gate) -- `app/layouts/` — `default`, `admin`, `landing`, `coming-soon` -- `server/api/` — Nitro API routes organized by feature: `auth/`, `events/`, `members/`, `helcim/`, `series/`, `updates/`, `admin/`, `slack/` -- `server/models/` — Mongoose schemas: `Member`, `Event`, `Series`, `Update` -- `server/utils/` — Service integrations: `mongoose.js`, `helcim.js`, `resend.js`, `slack.ts`, `tickets.js` +- Each member can enable peer support availability +- Set their own hours/frequency +- Cal.com handles scheduling +- Types of sessions: + - Peer support (30 min) + - Co-founder check-in (45 min) + - Practitioner office hours (60 min) -### Domain Model +**Matching System:** -Three membership **circles**: Community, Founder, Practitioner — each with different access and context. Five **contribution tiers**: $0, $5, $15, $30, $50/month via Helcim subscriptions. +- Simple questionnaire about current needs +- Suggest 3 potential peers based on: + - Complementary skills/needs + - Time zone compatibility + - Circle alignment (optional) +- Book directly via Cal.com links -Member statuses: `pending_payment`, `active`, `suspended`, `cancelled`. +## 6. Dashboard Design -Events support ticketing with circle-specific pricing overrides and can be grouped into Series with bundled passes. +### Personalized Sections -### Design System +**Welcome Block:** -- **Colors:** `guild-*` (warm neutral), `candlelight-*` (amber/gold accent), `parchment-*` (cream surfaces), `ember-*` (rust accent), `earth-*` (brown/ochre) — defined in `app/assets/css/main.css` -- **Circle tokens:** `--color-circle-community`, `--color-circle-founder`, `--color-circle-practitioner` with `-light`, `-dark`, `-bg` variants -- **Typography:** Inter (body), Quietism (display/headers, self-hosted from `public/fonts/`), Ubuntu Mono (code) -- **Theme:** `primary: amber`, `neutral: stone` — configured in `app/app.config.ts` -- **Effects:** `.candlelight-glow`, `.warm-text`, `.ink-grain`, `.paper-texture`, `.woodcut-border`, `.guild-stamp`, `.halftone-texture`, `.dithered-bg`, `.dithered-warm` -- **Content:** `.prose-guild` class for wiki/long-form content with warm palette and Quietism headings +- "Welcome back, [Name]" +- Your circle: [Circle] | Your contribution: $X/month +- Quick stats: Days as member, events attended, peers met -### Environment +**Community Pulse:** -Copy `.env.example` to `.env`. Required: `MONGODB_URI`, `JWT_SECRET`, `RESEND_API_KEY`, `HELCIM_API_TOKEN`, `SLACK_BOT_TOKEN`. Public vars are prefixed `NUXT_PUBLIC_`. The `NUXT_PUBLIC_COMING_SOON` flag gates access behind a launch page. +- Recent member updates (mini blog posts) +- Upcoming events this week +- New resources added +- New members to welcome -## Conventions +**Your Activity:** -- All frontend code is plain JavaScript (not TypeScript), using Vue 3 Composition API -- Server utilities auto-imported by Nitro — no explicit imports needed in API routes -- Use `USwitch` (not `UToggle`) — this is the correct Nuxt UI 3+ component name -- No fallback/placeholder data — always use real data -- Follow Nuxt 4 file-based routing conventions for route naming -- Always check Nuxt UI 3 latest documentation on the web when implementing UI components +- Your upcoming events +- Scheduled peer sessions +- Recent discussions you're in +- Resources you've bookmarked -## Product Spec +**Take Action:** -The sections below describe planned and in-progress features for reference. +- Post an update +- Propose an event +- Book a peer session +- Browse resources +- Update profile -### Member Features -- Profiles with privacy controls (public/members-only/private per field) -- Member updates/mini blog with rich text and images -- Peer support system with Cal.com integration for 1:1 scheduling +**Impact Metrics:** -### Events System -- RSVP with capacity limits and waitlist management -- Calendar export (.ics), ticketing, series passes -- Member-proposed events with interest threshold +- Total solidarity spots funded +- Events hosted this month +- Active members this week +- Resources shared -### Resources (Planned) -- Learning paths by circle, templates and tools, case studies -- Tag by circle relevance, download tracking, version control +## 7. Collaborative Tools -### Implementation Priority -**Must have:** Payment processing, Slack automation, member dashboard, resource library, event listing/RSVP -**Nice to have:** Member profiles, peer matching, Cal.com, member updates -**Post-launch:** Etherpad integration, member-proposed events, advanced search, analytics dashboard +### Etherpad Integration + +**Use Cases:** + +- Meeting notes templates +- Collaborative resource creation +- Event planning documents +- Shared learning notes + +**Implementation:** + +- Self-hosted Etherpad instance +- SSO with Ghost Guild accounts +- Auto-save and version history +- Export to multiple formats +- Embed in event pages for notes + +### Living Documents + +- Community-maintained guides +- Glossaries and definitions +- Frequently asked questions +- Best practices collections + +## 8. Technical Infrastructure + +### Notification System + +**Channels:** + +- Email (via Resend) +- In-app notifications +- Slack integration via bot + +**Configurable Preferences:** + +- Event reminders +- New resources in your area +- Peer session invitations +- Member updates digest +- Community announcements + +### Search & Discovery + +- Full-text search across: + - Resources + - Member profiles + - Event descriptions + - Member updates +- Filter by circle, tags, date +- Save searches for alerts + +### Analytics & Reporting + +- Member engagement metrics +- Resource usage stats +- Event attendance patterns +- Contribution distribution +- Circle movement tracking + +## 9. Content for Launch + +### Essential Content Pieces + +1. **Welcome Video** - Jennie & Eileen introduce Ghost Guild +2. **How This Works** - Clear explanation of circles and contributions +3. **Circle Guides** - What to expect in each circle +4. **Solidarity Economics** - Practical examples from gaming +5. **Getting Started Checklist** - First week actions + +### Pre-Populated Content + +- 10-15 essential resources per circle +- 3-5 upcoming events scheduled +- Sample member updates to show activity +- FAQ based on pre-registration questions + +## 10. Launch Strategy + +### Soft Launch (Week Before) + +- Invite 10-15 friendly testers +- Each from different backgrounds/circles +- Gather feedback on: + - Onboarding flow + - Resource organization + - Event system + - Profile creation + +### Launch Week + +**Day 1-2:** PA alumni and close network + +- Personal invitations +- Extra support available +- Gather testimonials + +**Day 3-4:** Gamma Space announcement + +- Post in relevant channels +- Host info session + +**Day 5-7:** Public launch + +- Email pre-registration list +- Social media announcement +- Open registration + +### Success Metrics + +**Week 1:** + +- 30 members across all circles +- 80% complete profiles +- 50% attend first event + +**Month 1:** + +- 75 active members +- 5 member-proposed events +- 20 peer sessions booked +- 90% Slack participation + +## 11. Ongoing Operations + +### Weekly Tasks + +- Review member proposals for events +- Process Gamma Space channel access +- Update resource library +- Send member spotlight + +### Monthly Tasks + +- Impact report to members +- Review and adjust contribution distribution +- Plan next month's events +- Gather member feedback + +### Quarterly Reviews + +- Assess circle definitions +- Evaluate pricing model +- Review platform features +- Plan new initiatives + +--- + +## Implementation Priority Order + +### Must Have for Launch + +1. Payment processing (Helcim) +2. Basic Slack automation +3. Member dashboard +4. Simple resource library +5. Event listing and RSVP + +### Nice to Have for Launch + +7. Member profiles +8. Peer matching system +9. Cal.com integration +10. Member updates/blog + +### Can Build Post-Launch + +11. Etherpad integration +12. Member-proposed events +13. Advanced search +14. Analytics dashboard +15. Monthly themes diff --git a/HELCIM_PAYMENT_FIX.md b/HELCIM_PAYMENT_FIX.md new file mode 100644 index 0000000..c1850b3 --- /dev/null +++ b/HELCIM_PAYMENT_FIX.md @@ -0,0 +1,149 @@ +# Helcim Payment Flow Fix + +## Issue +The initial implementation had a mismatch in the payment flow: +- **Error**: "Customer ID is required" when attempting to purchase event tickets +- **Cause**: The payment initialization endpoint required a `customerId`, but event tickets are one-time purchases that don't need customer accounts + +## Solution + +### 1. Updated Payment Initialization (`server/api/helcim/initialize-payment.post.js`) + +**Changed from:** +- Always requiring `customerId` +- Always using `verify` payment type (for card verification) +- Amount fixed at 0 + +**Changed to:** +- `customerId` is now optional +- Detects event ticket purchases via `metadata.type === 'event_ticket'` +- Uses `purchase` type for event tickets with amount > 0 +- Uses `verify` type for subscription setup (card verification) + +```javascript +// For event tickets: HelcimPay.js completes the purchase immediately +const paymentType = isEventTicket && amount > 0 ? 'purchase' : 'verify' +``` + +### 2. Updated Ticket Purchase Endpoint (`server/api/events/[id]/tickets/purchase.post.js`) + +**Changed from:** +- Expecting `paymentToken` from client +- Calling `processHelcimPayment()` to process the payment +- Payment happens server-side after modal closes + +**Changed to:** +- Expecting `transactionId` from client +- Payment already completed by HelcimPay.js modal +- Server just records the transaction + +**Why?** +When using HelcimPay.js with `purchase` type, the payment is processed inside the modal and we get a completed transaction back. We don't need to make a second API call to charge the card. + +### 3. Updated Frontend Component (`app/components/EventTicketPurchase.vue`) + +**Changed from:** +- Getting `cardToken` from payment modal +- Sending `paymentToken` to purchase endpoint + +**Changed to:** +- Getting `transactionId` from payment modal +- Sending `transactionId` to purchase endpoint +- Added validation to ensure transaction ID exists + +## Payment Flow Comparison + +### Old Flow (Subscriptions) +``` +1. Initialize payment session (verify mode, amount: 0) +2. User enters card in modal +3. Modal returns cardToken +4. Send cardToken to server +5. Server calls Helcim API to charge card +6. Create registration +``` + +### New Flow (Event Tickets) +``` +1. Initialize payment session (purchase mode, amount: actual price) +2. User enters card in modal +3. Helcim charges card immediately +4. Modal returns transactionId +5. Send transactionId to server +6. Server records transaction and creates registration +``` + +## Benefits + +1. **Simpler**: One API call instead of two +2. **Faster**: Payment completes in the modal +3. **More Secure**: No need to handle card tokens server-side +4. **PCI Compliant**: Card data never touches our server +5. **Better UX**: User sees immediate payment confirmation + +## Testing + +### Free Member Tickets +```bash +# Should work without payment modal +1. Member logs in +2. Views event with member.isFree: true +3. Fills name/email +4. Clicks "Complete Registration" +5. ✓ Registers immediately (no payment) +``` + +### Paid Public Tickets +```bash +# Should trigger Helcim modal +1. Non-member views event +2. Sees public ticket price +3. Fills name/email +4. Clicks "Pay $XX.XX" +5. Helcim modal opens +6. Enters test card: 4242 4242 4242 4242 +7. Payment processes +8. ✓ Modal closes with success +9. ✓ Registration created with transaction ID +``` + +## Environment Variables + +Ensure these are set in `.env`: + +```bash +# Public (client-side) +NUXT_PUBLIC_HELCIM_TOKEN=your_helcim_api_token +NUXT_PUBLIC_HELCIM_ACCOUNT_ID=your_account_id + +# Private (server-side) +HELCIM_API_TOKEN=your_helcim_api_token +``` + +## Common Issues + +### "Customer ID is required" +- ✅ **Fixed** - This error should no longer occur for event tickets +- If you still see it, check that `metadata.type: 'event_ticket'` is being passed + +### "No transaction ID received" +- Check browser console for HelcimPay.js errors +- Verify Helcim credentials are correct +- Ensure test mode is enabled for testing + +### Payment modal doesn't open +- Check that HelcimPay.js script loaded (see console) +- Verify `NUXT_PUBLIC_HELCIM_TOKEN` is set +- Check browser console for initialization errors + +## Files Changed + +1. `server/api/helcim/initialize-payment.post.js` - Smart payment type detection +2. `server/api/events/[id]/tickets/purchase.post.js` - Accept transactionId instead of token +3. `app/components/EventTicketPurchase.vue` - Pass transactionId instead of cardToken + +--- + +**Status**: ✅ Fixed +**Date**: 2025-10-14 +**Impact**: Event ticket purchases now work correctly with Helcim diff --git a/HELCIM_TEST_INSTRUCTIONS.md b/HELCIM_TEST_INSTRUCTIONS.md new file mode 100644 index 0000000..d1534e8 --- /dev/null +++ b/HELCIM_TEST_INSTRUCTIONS.md @@ -0,0 +1,120 @@ +# Helcim Integration Testing Guide + +## Setup Complete +The Helcim Recurring API integration has been set up with the following components: + +### 1. Composables +- `/app/composables/useHelcim.js` - Server-side Helcim API interactions +- `/app/composables/useHelcimPay.js` - Client-side HelcimPay.js integration + +### 2. Server API Endpoints +- `/server/api/helcim/customer.post.js` - Creates Helcim customer and member record +- `/server/api/helcim/subscription.post.js` - Creates subscription for paid tiers +- `/server/api/helcim/verify-payment.post.js` - Verifies payment token + +### 3. Updated Pages +- `/app/pages/join.vue` - Multi-step signup flow with payment integration + +### 4. Database Schema +- Updated `/server/models/member.js` with subscription fields + +## Testing Instructions + +### Prerequisites +1. Ensure your `.env` file has the test Helcim token: + ``` + NUXT_PUBLIC_HELCIM_TOKEN=your_test_token_here + ``` + +2. Ensure you have test payment plans created in Helcim dashboard matching these IDs: + - `supporter-monthly-5` + - `member-monthly-15` + - `advocate-monthly-30` + - `champion-monthly-50` + +### Test Flow + +#### 1. Start the Development Server +```bash +npm run dev +``` + +#### 2. Test Free Tier Signup +1. Navigate to `/join` +2. Fill in name and email +3. Select any circle +4. Choose "$0 - I need support right now" +5. Click "Complete Registration" +6. Should go directly to confirmation without payment + +#### 3. Test Paid Tier Signup +1. Navigate to `/join` +2. Fill in test details: + - Name: Test User + - Email: test@example.com +3. Select any circle +4. Choose a paid contribution tier (e.g., "$15 - I can sustain the community") +5. Click "Continue to Payment" +6. On the payment step, use Helcim test card numbers: + - **Success**: 4111 1111 1111 1111 + - **Decline**: 4000 0000 0000 0002 + - CVV: Any 3 digits + - Expiry: Any future date +7. Click "Complete Payment" +8. Should see confirmation with member details + +### Test Card Numbers (Helcim Test Mode) +- **Visa Success**: 4111 1111 1111 1111 +- **Mastercard Success**: 5500 0000 0000 0004 +- **Amex Success**: 3400 0000 0000 009 +- **Decline**: 4000 0000 0000 0002 +- **Insufficient Funds**: 4000 0000 0000 0051 + +### Debugging + +#### Check API Responses +Open browser DevTools Network tab to monitor: +- `/api/helcim/customer` - Should return customer ID and token +- `/api/helcim/verify-payment` - Should return card details +- `/api/helcim/subscription` - Should return subscription ID + +#### Common Issues + +1. **HelcimPay.js not loading** + - Check console for script loading errors + - Verify token is correctly set in environment + +2. **Customer creation fails** + - Check API token permissions in Helcim dashboard + - Verify MongoDB connection + +3. **Payment verification fails** + - Ensure you're using test card numbers + - Check that Helcim account is in test mode + +4. **Subscription creation fails** + - Verify payment plan IDs exist in Helcim + - Check that card token was successfully captured + +### Database Verification + +Check MongoDB for created records: +```javascript +// In MongoDB shell or client +db.members.findOne({ email: "test@example.com" }) +``` + +Should see: +- `helcimCustomerId` populated +- `helcimSubscriptionId` for paid tiers +- `status: "active"` after successful payment +- `paymentMethod: "card"` for paid tiers + +## Next Steps + +Once testing is successful: +1. Switch to production Helcim token +2. Create production payment plans in Helcim +3. Update plan IDs in `/app/config/contributions.js` if needed +4. Test with real payment card (small amount) +5. Set up webhook endpoints for subscription events (renewals, failures, cancellations) \ No newline at end of file diff --git a/HELCIM_TICKET_INTEGRATION.md b/HELCIM_TICKET_INTEGRATION.md new file mode 100644 index 0000000..265e02c --- /dev/null +++ b/HELCIM_TICKET_INTEGRATION.md @@ -0,0 +1,284 @@ +# Helcim Event Ticketing Integration - Implementation Summary + +## Overview +Successfully integrated Helcim payment processing with Ghost Guild's event ticketing system to support paid events, member pricing, early bird discounts, and capacity management. + +## What Was Built + +### 1. Enhanced Event Model (`server/models/event.js`) +Added comprehensive ticket schema with: +- **Member tickets**: Free/discounted pricing for members with circle-specific overrides +- **Public tickets**: Standard pricing with early bird support +- **Capacity management**: Overall event capacity tracking +- **Waitlist system**: Queue management when events sell out +- **Registration tracking**: Enhanced with ticket type, price paid, payment status, and refund info + +### 2. Ticket Business Logic (`server/utils/tickets.js`) +Created utility functions for: +- `calculateTicketPrice()` - Determines applicable price based on member status and early bird +- `checkTicketAvailability()` - Real-time availability checking +- `validateTicketPurchase()` - Pre-purchase validation +- `reserveTicket()` - Temporary reservation during checkout (prevents race conditions) +- `releaseTicket()` - Release abandoned reservations +- `completeTicketPurchase()` - Finalize purchase and update counts +- `addToWaitlist()` - Waitlist management +- `formatPrice()` - Consistent price formatting + +### 3. API Endpoints (`server/api/events/[id]/tickets/`) +Four new REST endpoints: + +#### `GET available.get.js` +- Check ticket availability and pricing for a user +- Returns: ticket type, price, availability, remaining spots +- Supports both authenticated (members) and public users + +#### `POST check-eligibility.post.js` +- Verify if user qualifies for member pricing +- Returns: member status and circle information + +#### `POST purchase.post.js` +- Complete ticket purchase with Helcim payment +- Validates availability, processes payment, creates registration +- Handles both free (member) and paid (public) tickets + +#### `POST reserve.post.js` +- Temporarily reserve ticket during checkout +- Prevents overselling during payment processing +- 10-minute TTL on reservations + +### 4. Frontend Components + +#### `EventTicketCard.vue` +Reusable ticket display component showing: +- Ticket name and description +- Price with early bird indicator +- Member savings comparison +- Availability status +- Waitlist option when sold out + +#### `EventTicketPurchase.vue` +Main ticket purchase flow component: +- Fetches ticket availability on load +- Displays appropriate ticket card +- Registration form (name, email) +- Integrated Helcim payment for paid tickets +- Success/error handling with toast notifications +- Shows "already registered" state + +### 5. Enhanced Composable (`app/composables/useHelcimPay.js`) +Added `initializeTicketPayment()` method: +- Ticket-specific payment initialization +- Includes event metadata for tracking +- Uses email as customer code for one-time purchases + +### 6. Updated Event Detail Page (`app/pages/events/[id].vue`) +- Detects if event has tickets enabled +- Shows new ticket system OR legacy registration form +- Maintains backward compatibility +- Handles ticket purchase success/error events + +### 7. Enhanced Email Templates (`server/utils/resend.js`) +Updated registration confirmation emails to include: +- Ticket type (Member/Public) +- Amount paid +- Transaction ID +- "Member Benefit" callout for free member tickets + +## How It Works + +### Free Member Event Flow +``` +1. Member views event → Sees "Free for Members" ticket +2. Fills in name/email → Click "Complete Registration" +3. System verifies membership → Creates registration +4. Sends confirmation email → Shows success message +``` + +### Paid Public Event Flow +``` +1. Public user views event → Sees ticket price +2. Fills in name/email → Clicks "Pay $XX.XX" +3. Helcim modal opens → User enters payment info +4. Payment processes → System creates registration +5. Sends confirmation with receipt → Shows success +``` + +### Early Bird Pricing +``` +- Before deadline: Shows early bird price + countdown +- After deadline: Automatically switches to regular price +- Calculated server-side for security +``` + +## Configuration + +### Event Setup +To enable ticketing for an event, set in the event document: + +```javascript +{ + tickets: { + enabled: true, + currency: "CAD", + member: { + available: true, + isFree: true, // or set price + name: "Member Ticket", + description: "Free for Ghost Guild members" + }, + public: { + available: true, + price: 25.00, + quantity: 50, // or null for unlimited + earlyBirdPrice: 20.00, + earlyBirdDeadline: "2025-11-01T00:00:00Z", + name: "Public Ticket" + }, + capacity: { + total: 75 // Total capacity across all ticket types + } + } +} +``` + +### Circle-Specific Pricing Example +```javascript +{ + tickets: { + member: { + isFree: false, // Not free by default + price: 15.00, // Default member price + circleOverrides: { + community: { + isFree: true // Free for community circle + }, + founder: { + price: 10.00 // Discounted for founders + }, + practitioner: { + price: 5.00 // Heavily discounted for practitioners + } + } + } + } +} +``` + +## Migration Strategy + +### Backward Compatibility +- Legacy `pricing` field still supported +- Events without `tickets.enabled` use old registration system +- Existing registrations work with new system + +### Converting Events to New System +```javascript +// Old format +{ + pricing: { + isFree: false, + publicPrice: 25, + paymentRequired: true + } +} + +// New format +{ + tickets: { + enabled: true, + member: { + isFree: true + }, + public: { + available: true, + price: 25 + } + } +} +``` + +## Testing Checklist + +### Member Ticket Flow +- [ ] Member can see free ticket +- [ ] Email pre-fills if logged in +- [ ] Registration completes without payment +- [ ] Confirmation email shows member benefit +- [ ] "Already registered" state shows correctly + +### Public Ticket Flow +- [ ] Public user sees correct price +- [ ] Helcim modal opens on submit +- [ ] Payment processes successfully +- [ ] Transaction ID saved to registration +- [ ] Confirmation email includes receipt + +### Early Bird Pricing +- [ ] Early bird price shows before deadline +- [ ] Countdown timer displays correctly +- [ ] Regular price shows after deadline +- [ ] Price calculation is server-side + +### Capacity Management +- [ ] Ticket count decrements on purchase +- [ ] Sold out message shows when full +- [ ] Waitlist option appears if enabled +- [ ] No overselling (test concurrent purchases) + +### Edge Cases +- [ ] Already registered users see status +- [ ] Cancelled events show cancellation message +- [ ] Past events don't allow registration +- [ ] Member-only events gate non-members +- [ ] Payment failures don't create registrations + +## Future Enhancements + +### Phase 2 Features +1. **Waitlist Notifications**: Auto-email when spots open +2. **Refund Processing**: Handle ticket cancellations with refunds +3. **Ticket Types**: Multiple ticket tiers per event +4. **Group Tickets**: Purchase multiple tickets at once +5. **Promo Codes**: Discount code support +6. **Admin Dashboard**: View sales, export attendee lists + +### Phase 3 Features +1. **Recurring Events**: Auto-apply tickets to series +2. **Transfer Tickets**: Allow users to transfer registrations +3. **PDF Tickets**: Generate printable/QR code tickets +4. **Revenue Analytics**: Track ticket sales and revenue +5. **Dynamic Pricing**: Adjust prices based on demand + +## Files Created +- `server/utils/tickets.js` (420 lines) +- `server/api/events/[id]/tickets/available.get.js` (150 lines) +- `server/api/events/[id]/tickets/purchase.post.js` (180 lines) +- `server/api/events/[id]/tickets/check-eligibility.post.js` (50 lines) +- `server/api/events/[id]/tickets/reserve.post.js` (80 lines) +- `app/components/EventTicketCard.vue` (195 lines) +- `app/components/EventTicketPurchase.vue` (330 lines) + +## Files Modified +- `server/models/event.js` - Enhanced ticket schema + registration fields +- `app/pages/events/[id].vue` - Integrated ticket UI +- `app/composables/useHelcimPay.js` - Added ticket payment method +- `server/utils/resend.js` - Enhanced email with ticket info + +## Total Implementation +- **~1,400 lines of code** across 11 files +- **4 new API endpoints** +- **2 new Vue components** +- **10+ utility functions** +- **Fully backward compatible** + +## Support +For questions or issues: +- Check Helcim API docs: https://docs.helcim.com +- Review CLAUDE.md for project context +- Test in development with Helcim test credentials + +--- + +**Status**: ✅ Implementation Complete +**Last Updated**: 2025-10-14 +**Developer**: Claude (Anthropic) diff --git a/README.md b/README.md new file mode 100644 index 0000000..f16d76d --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# Ghost Guild is a Nuxt 4 Site + +Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more. + +## Setup + +Make sure to install dependencies: + +```bash +# npm +npm install + +# pnpm +pnpm install + +# yarn +yarn install + +# bun +bun install +``` + +## Development Server + +Start the development server on `http://localhost:3000`: + +```bash +# npm +npm run dev + +# pnpm +pnpm dev + +# yarn +yarn dev + +# bun +bun run dev +``` + +## Production + +Build the application for production: + +```bash +# npm +npm run build + +# pnpm +pnpm build + +# yarn +yarn build + +# bun +bun run build +``` + +Locally preview production build: + +```bash +# npm +npm run preview + +# pnpm +pnpm preview + +# yarn +yarn preview + +# bun +bun run preview +``` + +Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information. diff --git a/SERIES_TICKETING_IMPLEMENTATION.md b/SERIES_TICKETING_IMPLEMENTATION.md new file mode 100644 index 0000000..4697ace --- /dev/null +++ b/SERIES_TICKETING_IMPLEMENTATION.md @@ -0,0 +1,400 @@ +# Series Ticketing Implementation + +## Overview +Comprehensive ticketing system for event series that allows users to purchase a single pass for all events in a series, or optionally register for individual events (drop-in model). + +## Two Ticketing Models Supported + +### 1. Series-Level Ticketing (Default) +**Intent:** Registrants attend all sessions in the series + +**Use cases:** +- Workshop series (e.g., 4-week game dev workshop) +- Courses +- Multi-day events +- Any series where commitment to all sessions is expected + +**How it works:** +- Purchase ONE series pass that grants access to ALL events +- Single payment covers the entire series +- Automatic registration for all events upon purchase +- User receives single confirmation email with full schedule + +### 2. Event-Level Ticketing (Drop-in) +**Intent:** Each event can be attended independently + +**Use cases:** +- Recurring meetups (e.g., monthly community calls) +- Tournaments where sessions are standalone +- Drop-in events where flexibility is key + +**How it works:** +- Each event has its own ticket configuration +- Users register per event, not for the series +- Can attend just one, some, or all events +- Standard event ticketing applies + +## What Was Built + +### 1. Data Models + +#### New Series Model (`server/models/series.js`) +Dedicated model for managing event series with: +- Series metadata (title, description, type, dates) +- Full ticket configuration (member/public pricing, capacity) +- Circle-specific pricing overrides +- Early bird pricing support +- Waitlist functionality +- Series registrations tracking +- Event registration references + +#### Enhanced Event Model +Added fields to `server/models/event.js`: +- `tickets.requiresSeriesTicket` - Flag to indicate series pass is required +- `tickets.seriesTicketReference` - Reference to Series model +- `registrations.ticketType` - Added "series_pass" option +- `registrations.isSeriesTicketHolder` - Boolean flag +- `registrations.seriesTicketId` - Reference to series registration + +### 2. Backend Logic + +#### Series Ticket Utilities (`server/utils/tickets.js`) +Added 8 new functions: +- `calculateSeriesTicketPrice()` - Pricing with member/circle overrides +- `checkSeriesTicketAvailability()` - Real-time availability checking +- `validateSeriesTicketPurchase()` - Pre-purchase validation +- `reserveSeriesTicket()` - Temporary reservation during checkout +- `releaseSeriesTicket()` - Release abandoned reservations +- `completeSeriesTicketPurchase()` - Finalize purchase and update counts +- `checkUserSeriesPass()` - Verify if user has valid pass +- `registerForAllSeriesEvents()` - Bulk registration across all events + +### 3. API Endpoints + +#### Series Ticket Endpoints +- `GET /api/series/[id]/tickets/available` - Check pass availability and pricing +- `POST /api/series/[id]/tickets/purchase` - Complete series pass purchase +- `POST /api/series/[id]/tickets/check-eligibility` - Verify member status +- `GET /api/events/[id]/check-series-access` - Verify series pass ownership for event + +### 4. Frontend Components + +#### EventSeriesTicketCard.vue +Beautiful purple-themed card displaying: +- Series pass name and pricing +- What's included (all events listed) +- Member savings comparison +- Event schedule preview (first 3 events) +- Availability status +- Member benefit callouts + +#### SeriesPassPurchase.vue +Complete purchase flow component: +- Loads series pass information +- Registration form (name/email) +- Helcim payment integration for paid passes +- Success/error handling with toast notifications +- Automatic registration for all events +- Email confirmation + +#### Updated EventTicketPurchase.vue +Enhanced to detect series pass requirements: +- Checks if event requires series pass +- Shows "Series Pass Required" message with link to series +- Displays "Registered via Series Pass" for pass holders +- Graceful fallback to regular ticketing + +### 5. Email Templates + +#### Series Pass Confirmation Email +Professional HTML email featuring: +- Purple gradient header +- Full series pass details +- Complete event schedule with dates/times +- Member benefit callout (if applicable) +- Transaction ID (if paid) +- "What's Next" guidance +- Dashboard link + +### 6. UI Integration + +#### Updated Series Detail Page (`/series/[id].vue`) +- New "Get Your Series Pass" section +- Displays SeriesPassPurchase component +- Shows only if `series.tickets.enabled` is true +- Refreshes data after successful purchase +- User session integration + +## Configuration Examples + +### Example 1: Free Member Series, Paid Public + +```javascript +const series = { + id: "coop-game-dev-2025", + title: "Cooperative Game Development Workshop Series", + type: "workshop_series", + tickets: { + enabled: true, + requiresSeriesTicket: true, // Must buy series pass + allowIndividualEventTickets: false, + currency: "CAD", + member: { + available: true, + isFree: true, + name: "Member Series Pass", + description: "Free for Ghost Guild members" + }, + public: { + available: true, + price: 100.00, + earlyBirdPrice: 80.00, + earlyBirdDeadline: "2025-11-01T00:00:00Z", + name: "Public Series Pass", + description: "Access to all 4 workshops" + }, + capacity: { + total: 30 // Total capacity across all ticket types + } + } +} +``` + +### Example 2: Circle-Specific Pricing + +```javascript +const series = { + tickets: { + enabled: true, + member: { + isFree: false, + price: 50.00, // Default member price + circleOverrides: { + community: { + isFree: true // Free for community circle + }, + founder: { + price: 25.00 // Discounted for founders + }, + practitioner: { + price: 10.00 // Heavily discounted + } + } + } + } +} +``` + +### Example 3: Drop-in Series (Individual Event Tickets) + +```javascript +const series = { + id: "monthly-meetup-2025", + title: "Monthly Community Meetup", + type: "recurring_meetup", + tickets: { + enabled: false, // No series-level tickets + requiresSeriesTicket: false, + allowIndividualEventTickets: true + } +} + +// Each event in series has its own tickets config +const event = { + series: { + id: "monthly-meetup-2025", + isSeriesEvent: true + }, + tickets: { + enabled: true, + member: { + isFree: true + }, + public: { + available: true, + price: 10.00 + } + } +} +``` + +## User Flows + +### Flow 1: Member Purchases Free Series Pass + +1. Member visits series page (`/series/coop-game-dev-2025`) +2. Sees "Free for Members" series pass card +3. Fills in name/email (pre-filled from session) +4. Clicks "Complete Registration" +5. System creates series registration +6. System registers user for all 4 events automatically +7. User receives series pass confirmation email with full schedule +8. Page refreshes showing "You're Registered!" status + +### Flow 2: Public User Purchases Paid Series Pass + +1. Public user visits series page +2. Sees "$100 Series Pass" (or $80 early bird) +3. Fills in name/email +4. Clicks "Pay $100.00" +5. Helcim payment modal opens +6. User completes payment +7. System processes payment and creates registrations +8. User receives confirmation email with transaction ID +9. Success toast shows "You're registered for all 4 events" + +### Flow 3: User with Series Pass Views Individual Event + +1. User has series pass for "Coop Game Dev Series" +2. Visits individual event page (`/events/workshop-1`) +3. EventTicketPurchase component checks series access +4. Sees "You're Registered!" with message: + "You have access to this event via your series pass for Cooperative Game Development Workshop Series" +5. No payment or registration required + +### Flow 4: User Without Series Pass Views Required Event + +1. User visits event that requires series pass +2. EventTicketPurchase component detects requirement +3. Sees purple banner: "Series Pass Required" +4. Message explains event is part of series +5. "View Series & Purchase Pass" button +6. Clicking redirects to series page to buy pass + +## Database Schema + +### Series Document Structure + +```javascript +{ + _id: ObjectId, + id: "coop-game-dev-2025", // String identifier + slug: "cooperative-game-development-workshop-series", + title: "Cooperative Game Development Workshop Series", + description: "Learn to build co-op games...", + type: "workshop_series", + isVisible: true, + isActive: true, + startDate: ISODate("2025-11-15"), + endDate: ISODate("2025-12-06"), + totalEvents: 4, + tickets: { + enabled: true, + requiresSeriesTicket: true, + currency: "CAD", + member: { /* ... */ }, + public: { /* ... */ }, + capacity: { total: 30, reserved: 2 } + }, + registrations: [ + { + memberId: ObjectId, + name: "Jane Doe", + email: "jane@example.com", + ticketType: "member", + ticketPrice: 0, + paymentStatus: "not_required", + registeredAt: ISODate, + eventRegistrations: [ + { eventId: ObjectId, registrationId: ObjectId }, + { eventId: ObjectId, registrationId: ObjectId } + ] + } + ], + targetCircles: ["founder", "practitioner"], + createdBy: "admin", + createdAt: ISODate, + updatedAt: ISODate +} +``` + +## Backward Compatibility + +✅ **Fully backward compatible** +- Events without series tickets work as before +- Legacy pricing model still supported +- Existing registrations unaffected +- Series can exist without ticket system enabled + +## Testing Checklist + +### Series Pass Purchase +- [ ] Member can see free series pass +- [ ] Public user sees correct pricing +- [ ] Early bird pricing displays correctly +- [ ] Helcim payment modal opens for paid passes +- [ ] Payment processes successfully +- [ ] All events get registrations created +- [ ] Confirmation email sends with all events listed +- [ ] Capacity decrements correctly + +### Event Access Control +- [ ] User with series pass sees "Registered" on event pages +- [ ] User without pass sees "Series Pass Required" message +- [ ] Link to series page works correctly +- [ ] Individual event tickets disabled when series pass required + +### Edge Cases +- [ ] Already registered users see correct message +- [ ] Sold out series shows waitlist option +- [ ] Cancelled events don't allow registration +- [ ] Payment failures don't create registrations +- [ ] Concurrent purchases don't oversell + +## Future Enhancements + +### Phase 2 (Planned) +- Series pass transfers between users +- Partial series passes ("Any 3 of 5 workshops") +- Upgrade from individual ticket to series pass +- Series pass refunds and cancellations +- Admin dashboard for series pass holders +- Export attendee lists by series + +### Phase 3 (Nice to Have) +- Gift series passes +- Group/team series passes +- Installment payment plans +- Series completion certificates +- Recurring series subscriptions + +## Files Created/Modified + +### New Files (9) +- `server/models/series.js` - Series data model +- `server/api/series/[id]/tickets/available.get.js` - Check availability +- `server/api/series/[id]/tickets/purchase.post.js` - Purchase series pass +- `server/api/series/[id]/tickets/check-eligibility.post.js` - Member check +- `server/api/events/[id]/check-series-access.get.js` - Verify pass ownership +- `app/components/EventSeriesTicketCard.vue` - Series pass display card +- `app/components/SeriesPassPurchase.vue` - Purchase flow component +- `SERIES_TICKETING_IMPLEMENTATION.md` - This documentation + +### Modified Files (4) +- `server/models/event.js` - Added series ticket fields +- `server/utils/tickets.js` - Added 8 series ticket functions (~360 lines) +- `server/utils/resend.js` - Added series pass confirmation email +- `app/components/EventTicketPurchase.vue` - Series pass detection +- `app/pages/series/[id].vue` - Integrated purchase UI + +### Total Implementation +- **~1,200 lines of new code** +- **9 new files** +- **4 modified files** +- **4 new API endpoints** +- **2 new Vue components** +- **8 new utility functions** +- **1 new data model** + +## Support + +For questions or issues: +- Review this documentation +- Check existing ticketing docs: `HELCIM_TICKET_INTEGRATION.md` +- Reference project context: `CLAUDE.md` + +--- + +**Status**: ✅ Implementation Complete +**Last Updated**: 2025-10-14 +**Developer**: Claude (Anthropic) diff --git a/UPDATE_SUMMARY.md b/UPDATE_SUMMARY.md new file mode 100644 index 0000000..5c7d439 --- /dev/null +++ b/UPDATE_SUMMARY.md @@ -0,0 +1,57 @@ +# Helcim Integration - Issues Fixed + +## Problem +The API was returning 401 Unauthorized when trying to create customers. + +## Root Cause +The runtime config wasn't properly accessing the Helcim token in server-side endpoints. + +## Solution Applied + +### 1. Fixed Runtime Config Access +Updated all server endpoints to: +- Pass the `event` parameter to `useRuntimeConfig(event)` +- Fallback to `process.env.NUXT_PUBLIC_HELCIM_TOKEN` if config doesn't load + +### 2. Files Updated +- `/server/api/helcim/customer.post.js` +- `/server/api/helcim/subscription.post.js` +- `/server/api/helcim/verify-payment.post.js` +- `/server/api/helcim/test-connection.get.js` + +### 3. Fixed Import Path +Created `/server/config/contributions.js` to re-export the contributions config for server-side imports. + +### 4. Verified Token Works +Created `test-helcim-direct.js` which successfully: +- Connected to Helcim API +- Created a test customer (ID: 32854583, Code: CST1000) + +## Testing Instructions + +1. Restart your development server: + ```bash + npm run dev + ``` + +2. Test the connection: + ```bash + curl http://localhost:3000/api/helcim/test-connection + ``` + +3. Try the signup flow at `/join` + +## Important Notes + +- The token in your `.env` file is working correctly +- The Helcim API is accessible and responding +- Customer creation is functional when called directly +- The issue was specifically with how Nuxt's runtime config was being accessed in server endpoints + +## Next Steps + +Once you confirm the signup flow works: +1. Test with different contribution tiers +2. Verify payment capture with test cards +3. Check that subscriptions are created correctly +4. Consider adding webhook endpoints for subscription events \ No newline at end of file diff --git a/app/app.config.ts b/app/app.config.ts index 60b0d47..7b45bff 100644 --- a/app/app.config.ts +++ b/app/app.config.ts @@ -1,7 +1,7 @@ export default defineAppConfig({ ui: { colors: { - primary: "amber", + primary: "emerald", neutral: "stone", }, formField: { diff --git a/app/assets/css/fonts.css b/app/assets/css/fonts.css index bb94cbf..fbcd1c2 100644 --- a/app/assets/css/fonts.css +++ b/app/assets/css/fonts.css @@ -1,43 +1,2 @@ -/* - * Font declarations for Ghost Guild - * - * Quietism: Display/heading font (serif) - * Place woff2 files in public/fonts/ - * Expected files: Quietism-Regular.woff2, Quietism-Medium.woff2, - * Quietism-Bold.woff2, Quietism-Italic.woff2 - * - * Inter and Ubuntu Mono are loaded via @nuxt/fonts module. - * See nuxt.config.ts for configuration. - */ - -@font-face { - font-family: "Quietism"; - src: url("/fonts/Quietism-Regular.woff2") format("woff2"); - font-weight: 400; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: "Quietism"; - src: url("/fonts/Quietism-Medium.woff2") format("woff2"); - font-weight: 500; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: "Quietism"; - src: url("/fonts/Quietism-Bold.woff2") format("woff2"); - font-weight: 700; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: "Quietism"; - src: url("/fonts/Quietism-Italic.woff2") format("woff2"); - font-weight: 400; - font-style: italic; - font-display: swap; -} +/* Font declarations are now handled by @nuxt/fonts module */ +/* See nuxt.config.ts for font configuration */ \ No newline at end of file diff --git a/app/assets/css/main.css b/app/assets/css/main.css index 5e7c6d3..37d236e 100644 --- a/app/assets/css/main.css +++ b/app/assets/css/main.css @@ -1,43 +1,3 @@ -/* - * Ghost Guild Design System - * ======================== - * - * Token naming convention: - * --color-{palette}-{shade} Color palettes (50-950 scale) - * --color-circle-{name}-{variant} Circle-specific tokens - * --font-{role} Font family tokens - * --text-{scale} Typographic scale - * --leading-{role} Line heights - * --tracking-{role} Letter spacing - * - * Palettes: - * guild-* Warm neutral ground (dark hall / workshop) - * candlelight-* Primary warm accent (amber/gold/ochre) - * parchment-* Light content surfaces (cream/vellum) - * ember-* Secondary warm accent (deeper amber/rust) - * earth-* Tertiary muted tones (brown/ochre) - * - * Usage guidelines: - * Backgrounds: guild-900/950 (dark), parchment-50/100 (light surfaces) - * Text primary: guild-100 (dark mode), guild-900 (light mode) - * Text secondary: guild-300/400 - * Borders: guild-700 (dark), guild-300 (light) - * Accents: candlelight-* for links, highlights, interactive - * Surfaces: parchment-* for readable content areas - * - * Effect classes: - * .ink-grain Subtle noise overlay (mix-blend-mode: overlay) - * .paper-texture Paper fiber overlay - * .woodcut-border Irregular line border via border-image - * .candlelight-glow Warm box-shadow (replaces .ethereal-glow) - * .warm-text Warm text-shadow (replaces .ethereal-text) - * .guild-stamp Circular decorative double-border frame - * .halftone-texture Dot pattern overlay (updated for warm palette) - * .dithered-bg Cross-hatch dither pattern - * .dithered-warm Amber-tinted dither variant - * .prose-guild Wiki/long-form content styling - */ - @import "./fonts.css"; @import "tailwindcss"; @import "@nuxt/ui"; @@ -49,229 +9,132 @@ --font-sans: "Inter", sans-serif; --font-body: "Inter", sans-serif; --font-mono: "Ubuntu Mono", monospace; - --font-display: "Quietism", serif; - --font-serif: "Quietism", serif; + --font-display: "NB Television Pro", monospace; - /* Guild - warm neutral ground (light mode: dark values for text on light bg) */ - --color-guild-50: #1a1510; - --color-guild-100: #2a241c; - --color-guild-200: #3a332a; - --color-guild-300: #524939; - --color-guild-400: #6b5f4d; - --color-guild-500: #8a7c68; - --color-guild-600: #a49585; - --color-guild-700: #bfb3a2; - --color-guild-800: #d9d0c3; - --color-guild-900: #f0ebe4; - --color-guild-950: #f8f5f0; + /* Ethereal color palette - light mode (inverted for light backgrounds) */ + --color-ghost-50: #0a0a0a; + --color-ghost-100: #1a1a1a; + --color-ghost-200: #2a2a2a; + --color-ghost-300: #3a3a3a; + --color-ghost-400: #4a4a4a; + --color-ghost-500: #6a6a6a; + --color-ghost-600: #8a8a8a; + --color-ghost-700: #b0b0b0; + --color-ghost-800: #d0d0d0; + --color-ghost-900: #f0f0f0; - /* Candlelight - primary warm accent (amber/gold/ochre) */ - --color-candlelight-50: #1f1708; - --color-candlelight-100: #3d2c0f; - --color-candlelight-200: #5c4118; - --color-candlelight-300: #7b5822; - --color-candlelight-400: #9a6f2c; - --color-candlelight-500: #b8873a; - --color-candlelight-600: #d09e4e; - --color-candlelight-700: #e0b86e; - --color-candlelight-800: #ecd09a; - --color-candlelight-900: #f5e6c5; - --color-candlelight-950: #faf2e0; + /* Subtle accent - barely visible blue-gray (light mode) */ + --color-whisper-50: #0f1419; + --color-whisper-100: #1a1f2e; + --color-whisper-200: #252d40; + --color-whisper-300: #2f3b52; + --color-whisper-400: #3a4964; + --color-whisper-500: #4f5d7a; + --color-whisper-600: #687291; + --color-whisper-700: #8491a8; + --color-whisper-800: #a8b3c7; + --color-whisper-900: #d4dae6; - /* Parchment - light content surfaces (cream/vellum) */ - --color-parchment-50: #1d1a14; - --color-parchment-100: #332e24; - --color-parchment-200: #4a4234; - --color-parchment-300: #635844; - --color-parchment-400: #7d6e56; - --color-parchment-500: #97866c; - --color-parchment-600: #afa088; - --color-parchment-700: #c8baa5; - --color-parchment-800: #ddd3c3; - --color-parchment-900: #f0ebe0; - --color-parchment-950: #f9f6f0; - - /* Ember - secondary warm accent (deeper amber/rust) */ - --color-ember-50: #1e120b; - --color-ember-100: #3b2215; - --color-ember-200: #58321f; - --color-ember-300: #76432a; - --color-ember-400: #945535; - --color-ember-500: #b26840; - --color-ember-600: #c87e55; - --color-ember-700: #da9a72; - --color-ember-800: #e8b899; - --color-ember-900: #f3d6c0; - --color-ember-950: #f9ebe0; - - /* Earth - tertiary muted tones (brown/ochre) */ - --color-earth-50: #17140e; - --color-earth-100: #2d271c; - --color-earth-200: #433a2a; - --color-earth-300: #5a4d39; - --color-earth-400: #726148; - --color-earth-500: #8a7658; - --color-earth-600: #a08c6d; - --color-earth-700: #b7a487; - --color-earth-800: #cebda4; - --color-earth-900: #e5d7c2; - --color-earth-950: #f2ece1; + /* Sparkle accent (light mode) */ + --color-sparkle-50: #202020; + --color-sparkle-100: #404040; + --color-sparkle-200: #606060; + --color-sparkle-300: #808080; + --color-sparkle-400: #a0a0a0; + --color-sparkle-500: #c0c0c0; + --color-sparkle-600: #d0d0d0; + --color-sparkle-700: #e8e8e8; + --color-sparkle-800: #f0f0f0; + --color-sparkle-900: #fafafa; } .dark { - /* Guild - warm neutral (dark mode: light values) */ - --color-guild-50: #f0ebe4; - --color-guild-100: #d9d0c3; - --color-guild-200: #bfb3a2; - --color-guild-300: #a49585; - --color-guild-400: #8a7c68; - --color-guild-500: #6b5f4d; - --color-guild-600: #524939; - --color-guild-700: #3a332a; - --color-guild-800: #2a241c; - --color-guild-900: #1a1510; - --color-guild-950: #110e0a; + /* Ethereal color palette - dark mode (original values) */ + --color-ghost-50: #f0f0f0; + --color-ghost-100: #d0d0d0; + --color-ghost-200: #b0b0b0; + --color-ghost-300: #8a8a8a; + --color-ghost-400: #6a6a6a; + --color-ghost-500: #4a4a4a; + --color-ghost-600: #3a3a3a; + --color-ghost-700: #2a2a2a; + --color-ghost-800: #1a1a1a; + --color-ghost-900: #0a0a0a; - /* Candlelight - primary warm accent (dark mode) */ - --color-candlelight-50: #faf2e0; - --color-candlelight-100: #f5e6c5; - --color-candlelight-200: #ecd09a; - --color-candlelight-300: #e0b86e; - --color-candlelight-400: #d09e4e; - --color-candlelight-500: #b8873a; - --color-candlelight-600: #9a6f2c; - --color-candlelight-700: #7b5822; - --color-candlelight-800: #5c4118; - --color-candlelight-900: #3d2c0f; - --color-candlelight-950: #1f1708; + /* Subtle accent - barely visible blue-gray (dark mode) */ + --color-whisper-50: #d4dae6; + --color-whisper-100: #a8b3c7; + --color-whisper-200: #8491a8; + --color-whisper-300: #687291; + --color-whisper-400: #4f5d7a; + --color-whisper-500: #3a4964; + --color-whisper-600: #2f3b52; + --color-whisper-700: #252d40; + --color-whisper-800: #1a1f2e; + --color-whisper-900: #0f1419; - /* Parchment - light content surfaces (dark mode) */ - --color-parchment-50: #f9f6f0; - --color-parchment-100: #f0ebe0; - --color-parchment-200: #ddd3c3; - --color-parchment-300: #c8baa5; - --color-parchment-400: #afa088; - --color-parchment-500: #97866c; - --color-parchment-600: #7d6e56; - --color-parchment-700: #635844; - --color-parchment-800: #4a4234; - --color-parchment-900: #332e24; - --color-parchment-950: #1d1a14; - - /* Ember - secondary warm accent (dark mode) */ - --color-ember-50: #f9ebe0; - --color-ember-100: #f3d6c0; - --color-ember-200: #e8b899; - --color-ember-300: #da9a72; - --color-ember-400: #c87e55; - --color-ember-500: #b26840; - --color-ember-600: #945535; - --color-ember-700: #76432a; - --color-ember-800: #58321f; - --color-ember-900: #3b2215; - --color-ember-950: #1e120b; - - /* Earth - tertiary muted tones (dark mode) */ - --color-earth-50: #f2ece1; - --color-earth-100: #e5d7c2; - --color-earth-200: #cebda4; - --color-earth-300: #b7a487; - --color-earth-400: #a08c6d; - --color-earth-500: #8a7658; - --color-earth-600: #726148; - --color-earth-700: #5a4d39; - --color-earth-800: #433a2a; - --color-earth-900: #2d271c; - --color-earth-950: #17140e; + /* Sparkle accent (dark mode) */ + --color-sparkle-50: #fafafa; + --color-sparkle-100: #f0f0f0; + --color-sparkle-200: #e8e8e8; + --color-sparkle-300: #d0d0d0; + --color-sparkle-400: #c0c0c0; + --color-sparkle-500: #a0a0a0; + --color-sparkle-600: #808080; + --color-sparkle-700: #606060; + --color-sparkle-800: #404040; + --color-sparkle-900: #202020; } -/* Circle-specific tokens */ +/* Global ethereal background - light mode */ :root { - /* Community - warm hearth amber */ - --color-circle-community: #b8873a; - --color-circle-community-light: #e0b86e; - --color-circle-community-dark: #7b5822; - --color-circle-community-bg: rgba(184, 135, 58, 0.1); - - /* Founder - forge copper/orange */ - --color-circle-founder: #b26840; - --color-circle-founder-light: #da9a72; - --color-circle-founder-dark: #76432a; - --color-circle-founder-bg: rgba(178, 104, 64, 0.1); - - /* Practitioner - deep gold/ochre */ - --color-circle-practitioner: #8a7658; - --color-circle-practitioner-light: #b7a487; - --color-circle-practitioner-dark: #5a4d39; - --color-circle-practitioner-bg: rgba(138, 118, 88, 0.1); - - /* Typographic scale */ - --text-display-xl: 3.5rem; - --text-display-lg: 2.5rem; - --text-display: 2rem; - --text-display-sm: 1.5rem; - --text-body-lg: 1.125rem; - --text-body: 1rem; - --text-body-sm: 0.875rem; - --text-caption: 0.75rem; - --text-overline: 0.6875rem; - - /* Line heights */ - --leading-display: 1.15; - --leading-body: 1.65; - - /* Letter spacing */ - --tracking-display: -0.02em; - --tracking-wide: 0.05em; - - /* Ambient background - warm radial gradients */ - --ambient-bg: + --ethereal-bg: radial-gradient( circle at 20% 80%, - rgba(184, 135, 58, 0.03) 0%, + rgba(40, 40, 40, 0.03) 0%, transparent 50% ), radial-gradient( circle at 80% 20%, - rgba(178, 104, 64, 0.02) 0%, + rgba(40, 40, 40, 0.02) 0%, transparent 50% ), radial-gradient( circle at 40% 40%, - rgba(138, 118, 88, 0.01) 0%, + rgba(40, 40, 40, 0.01) 0%, transparent 50% ); --halftone-pattern: radial-gradient( circle, - rgba(42, 36, 28, 0.1) 1px, + rgba(0, 0, 0, 0.1) 1px, transparent 1px ); --halftone-size: 8px 8px; } -/* Dark mode */ +/* Dark mode background */ .dark:root { - --ambient-bg: + --ethereal-bg: radial-gradient( circle at 20% 80%, - rgba(224, 184, 110, 0.03) 0%, + rgba(232, 232, 232, 0.03) 0%, transparent 50% ), radial-gradient( circle at 80% 20%, - rgba(218, 154, 114, 0.02) 0%, + rgba(232, 232, 232, 0.02) 0%, transparent 50% ), radial-gradient( circle at 40% 40%, - rgba(183, 164, 135, 0.01) 0%, + rgba(232, 232, 232, 0.01) 0%, transparent 50% ); --halftone-pattern: radial-gradient( circle, - rgba(240, 235, 228, 0.1) 1px, + rgba(255, 255, 255, 0.1) 1px, transparent 1px ); } @@ -281,41 +144,12 @@ html { } body { - background: var(--ambient-bg), #f0ebe4; + background: var(--ethereal-bg), #f0f0f0; background-attachment: fixed; } .dark body { - background: var(--ambient-bg), #1a1510; -} - -/* Display typography utilities */ -.text-display-xl { - font-family: var(--font-display); - font-size: var(--text-display-xl); - line-height: var(--leading-display); - letter-spacing: var(--tracking-display); -} - -.text-display-lg { - font-family: var(--font-display); - font-size: var(--text-display-lg); - line-height: var(--leading-display); - letter-spacing: var(--tracking-display); -} - -.text-display { - font-family: var(--font-display); - font-size: var(--text-display); - line-height: var(--leading-display); - letter-spacing: var(--tracking-display); -} - -.text-display-sm { - font-family: var(--font-display); - font-size: var(--text-display-sm); - line-height: var(--leading-display); - letter-spacing: var(--tracking-display); + background: var(--ethereal-bg), #0a0a0a; } /* Halftone texture overlay */ @@ -336,93 +170,106 @@ body { pointer-events: none; } -/* Craft/Material Effects */ +/* Sparkle effects */ +@keyframes sparkle { + 0%, + 100% { + opacity: 0.3; + transform: scale(0.8); + } + 50% { + opacity: 1; + transform: scale(1.2); + } +} -/* Warm box-shadow (replaces .ethereal-glow) */ -.candlelight-glow { +@keyframes twinkle { + 0%, + 100% { + opacity: 0.2; + } + 25% { + opacity: 0.8; + } + 75% { + opacity: 0.4; + } +} + +.sparkle-field { + position: relative; + overflow: hidden; +} + +.sparkle-field::after { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-image: + radial-gradient( + circle at 10% 20%, + var(--color-sparkle-200) 1px, + transparent 1px + ), + radial-gradient( + circle at 90% 80%, + var(--color-sparkle-400) 1px, + transparent 1px + ), + radial-gradient( + circle at 30% 70%, + var(--color-sparkle-200) 0.5px, + transparent 0.5px + ), + radial-gradient( + circle at 70% 30%, + var(--color-sparkle-400) 0.5px, + transparent 0.5px + ), + radial-gradient( + circle at 50% 10%, + var(--color-sparkle-200) 1px, + transparent 1px + ), + radial-gradient( + circle at 20% 90%, + var(--color-sparkle-400) 0.5px, + transparent 0.5px + ); + background-size: + 200px 200px, + 300px 300px, + 150px 150px, + 250px 250px, + 180px 180px, + 220px 220px; + animation: twinkle 4s infinite ease-in-out; + pointer-events: none; + opacity: 0.6; +} + +/* Ethereal glow effects */ +.ethereal-glow { box-shadow: - 0 0 20px rgba(184, 135, 58, 0.12), - 0 0 40px rgba(184, 135, 58, 0.06), - inset 0 1px 0 rgba(240, 235, 228, 0.08); + 0 0 20px rgba(232, 232, 232, 0.1), + 0 0 40px rgba(232, 232, 232, 0.05), + inset 0 1px 0 rgba(255, 255, 255, 0.1); } -/* Warm text-shadow (replaces .ethereal-text) */ -.warm-text { - text-shadow: 0 0 10px rgba(224, 184, 110, 0.25); -} - -/* Subtle noise overlay */ -.ink-grain { - position: relative; -} - -.ink-grain::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-image: url("/textures/grain.png"); - background-repeat: repeat; - background-size: 128px 128px; - mix-blend-mode: overlay; - opacity: 0.04; - pointer-events: none; -} - -/* Paper fiber overlay */ -.paper-texture { - position: relative; -} - -.paper-texture::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-image: url("/textures/paper.png"); - background-repeat: repeat; - background-size: 256px 256px; - opacity: 0.04; - pointer-events: none; -} - -/* Irregular line border */ -.woodcut-border { - border-image: repeating-linear-gradient( - 90deg, - var(--color-guild-700) 0px, - var(--color-guild-600) 2px, - var(--color-guild-700) 3px, - transparent 3px, - transparent 6px - ) 4; -} - -/* Circular decorative frame */ -.guild-stamp { - border: 2px solid var(--color-candlelight-600); - outline: 2px solid var(--color-candlelight-700); - outline-offset: 3px; - border-radius: 50%; -} - -/* Warm interior feel for content areas */ -.guild-interior { - background: var(--ambient-bg); - box-shadow: inset 0 2px 8px rgba(26, 21, 16, 0.06); +.ethereal-text { + text-shadow: 0 0 10px rgba(232, 232, 232, 0.3); } /* Dithered gradients */ .dithered-bg { background: - linear-gradient(45deg, var(--color-guild-800) 25%, transparent 25%), - linear-gradient(-45deg, var(--color-guild-800) 25%, transparent 25%), - linear-gradient(45deg, transparent 75%, var(--color-guild-700) 75%), - linear-gradient(-45deg, transparent 75%, var(--color-guild-700) 75%); + linear-gradient(45deg, var(--color-ghost-800) 25%, transparent 25%), + linear-gradient(-45deg, var(--color-ghost-800) 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, var(--color-ghost-700) 75%), + linear-gradient(-45deg, transparent 75%, var(--color-ghost-700) 75%); background-size: 4px 4px; background-position: 0 0, @@ -431,58 +278,6 @@ body { -2px 0px; } -/* Amber-tinted dither variant */ -.dithered-warm { - background: - linear-gradient(45deg, var(--color-candlelight-800) 25%, transparent 25%), - linear-gradient(-45deg, var(--color-candlelight-800) 25%, transparent 25%), - linear-gradient(45deg, transparent 75%, var(--color-candlelight-700) 75%), - linear-gradient(-45deg, transparent 75%, var(--color-candlelight-700) 75%); - background-size: 4px 4px; - background-position: - 0 0, - 0 2px, - 2px -2px, - -2px 0px; -} - -/* Prose overrides for guild wiki/long-form content */ -.prose-guild { - max-width: 72ch; -} - -.prose-guild h1, -.prose-guild h2, -.prose-guild h3, -.prose-guild h4 { - font-family: var(--font-display); - letter-spacing: var(--tracking-display); -} - -.prose-guild a { - color: var(--color-candlelight-600); - text-decoration-color: var(--color-candlelight-700); - text-underline-offset: 3px; -} - -.prose-guild a:hover { - color: var(--color-candlelight-500); - text-decoration-color: var(--color-candlelight-500); -} - -.prose-guild blockquote { - background-color: var(--color-parchment-900); - border-left-color: var(--color-candlelight-600); - border-left-width: 3px; - padding: 1rem 1.5rem; - border-radius: 0 0.5rem 0.5rem 0; -} - -.dark .prose-guild blockquote { - background-color: var(--color-parchment-900); - border-left-color: var(--color-candlelight-500); -} - /* Mobile responsive utilities */ @media (max-width: 1023px) { /* Prevent horizontal scroll on mobile */ diff --git a/app/components/AppFooter.vue b/app/components/AppFooter.vue index 8741d49..38b2f4e 100644 --- a/app/components/AppFooter.vue +++ b/app/components/AppFooter.vue @@ -7,7 +7,7 @@ >
+
© {{ currentYear }} Ghost Guild
+
Contact us: hello@ghostguild.org
@@ -61,30 +61,30 @@+
Enter your email to receive a login link
diff --git a/app/components/EventSeriesBadge.vue b/app/components/EventSeriesBadge.vue index a8b23a0..ed5a512 100644 --- a/app/components/EventSeriesBadge.vue +++ b/app/components/EventSeriesBadge.vue @@ -1,18 +1,18 @@{{ description }}
diff --git a/app/components/EventSeriesTicketCard.vue b/app/components/EventSeriesTicketCard.vue index a98e0a8..7580d8a 100644 --- a/app/components/EventSeriesTicketCard.vue +++ b/app/components/EventSeriesTicketCard.vue @@ -1,6 +1,6 @@+
{{ ticketInfo.description }}
+
Public price: {{ ticketInfo.publicTicket.formattedPrice }}
Loading ticket information...
+Loading ticket information...
+
Purchase a series pass to get access to all events in this series.
+
See you on {{ formatEventDate(eventStartDate) }}!
+
Using your member email
+
This event is currently at capacity. Join the waitlist to be notified if spots become available.
@@ -203,8 +203,8 @@ name="heroicons:x-circle" class="w-16 h-16 text-red-400 mx-auto mb-4" /> -+
Unfortunately, this event is at capacity and no longer accepting registrations.
diff --git a/app/components/LoginModal.vue b/app/components/LoginModal.vue index 543a936..064c7ed 100644 --- a/app/components/LoginModal.vue +++ b/app/components/LoginModal.vue @@ -5,12 +5,12 @@ :description="description" :dismissible="dismissible" :ui="{ - content: 'bg-guild-900 border border-guild-700', - header: 'bg-guild-900 border-b border-guild-700', - body: 'bg-guild-900', - footer: 'bg-guild-900 border-t border-guild-700', - title: 'text-guild-100', - description: 'text-guild-400', + content: 'bg-ghost-900 border border-ghost-700', + header: 'bg-ghost-900 border-b border-ghost-700', + body: 'bg-ghost-900', + footer: 'bg-ghost-900 border-t border-ghost-700', + title: 'text-ghost-100', + description: 'text-ghost-400', }" > @@ -20,9 +20,9 @@- We've sent a magic link to {{ loginForm.email }}. +
+ We've sent a magic link to {{ loginForm.email }}. Click the link to sign in.
+
We'll send you a secure magic link. No password needed!
+
Don't have an account?