From a62e167876fe457bd23ffcf9caac6ede0699491d Mon Sep 17 00:00:00 2001 From: Jennie Robinson Faber Date: Tue, 24 Feb 2026 20:01:11 +0000 Subject: [PATCH] Migrate design system from ethereal/cool to warm/craft/guild theme Replace ghost/whisper/sparkle color palettes with guild/candlelight/parchment/ember/earth tokens. Switch typography from NB Television Pro to Quietism serif. Update all 25 Vue components, layouts, and pages to new design system. Add circle color tokens, typography scale, prose-guild class, and warm texture effects. Clean up stale documentation files. --- .gitignore | 2 +- CLAUDE.md | 367 +++------------- HELCIM_PAYMENT_FIX.md | 149 ------- HELCIM_TEST_INSTRUCTIONS.md | 120 ------ HELCIM_TICKET_INTEGRATION.md | 284 ------------ README.md | 75 ---- SERIES_TICKETING_IMPLEMENTATION.md | 400 ----------------- UPDATE_SUMMARY.md | 57 --- app/app.config.ts | 2 +- app/assets/css/fonts.css | 45 +- app/assets/css/main.css | 521 ++++++++++++++++------- app/components/AppFooter.vue | 4 +- app/components/AppNavigation.vue | 28 +- app/components/EventSeriesBadge.vue | 10 +- app/components/EventSeriesTicketCard.vue | 22 +- app/components/EventTicketCard.vue | 18 +- app/components/EventTicketPurchase.vue | 24 +- app/components/LoginModal.vue | 32 +- app/components/MemberStatusBanner.vue | 4 +- app/components/PageHeader.vue | 22 +- app/components/PrivacyToggle.vue | 6 +- app/components/SeriesPassPurchase.vue | 2 +- app/components/UpdateCard.vue | 32 +- app/components/UpdateForm.vue | 44 +- app/config/circles.js | 64 +-- app/layouts/coming-soon.vue | 2 +- app/layouts/default.vue | 8 +- app/layouts/landing.vue | 131 ++++++ app/pages/about.vue | 141 ++++-- app/pages/events/[id].vue | 76 ++-- app/pages/events/index.vue | 144 +++---- app/pages/index.vue | 241 +++++++---- app/pages/member/dashboard.vue | 98 ++--- app/pages/member/my-updates.vue | 22 +- app/pages/member/profile.vue | 84 ++-- app/pages/members.vue | 46 +- app/pages/welcome.vue | 60 +-- public/fonts/.gitkeep | 0 public/textures/.gitkeep | 0 39 files changed, 1300 insertions(+), 2087 deletions(-) delete mode 100644 HELCIM_PAYMENT_FIX.md delete mode 100644 HELCIM_TEST_INSTRUCTIONS.md delete mode 100644 HELCIM_TICKET_INTEGRATION.md delete mode 100644 README.md delete mode 100644 SERIES_TICKETING_IMPLEMENTATION.md delete mode 100644 UPDATE_SUMMARY.md create mode 100644 app/layouts/landing.vue create mode 100644 public/fonts/.gitkeep create mode 100644 public/textures/.gitkeep diff --git a/.gitignore b/.gitignore index 8f7a3dd..400f35a 100644 --- a/.gitignore +++ b/.gitignore @@ -22,5 +22,5 @@ logs .env .env.* !.env.example -/*.md +/docs/ scripts/*.js diff --git a/CLAUDE.md b/CLAUDE.md index 0abd815..7cadf29 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,329 +1,94 @@ -## 2. Member Features +# CLAUDE.md -### Member Profiles +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. -**Core Fields:** +## Project Overview -- 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 +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. -**Privacy Controls:** +## Commands -- 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 +```bash +npm run dev # Start dev server at http://localhost:3000 +npm run build # Production build +npm run preview # Preview production build ``` -### Resource Features +No test framework is currently configured. -- 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 +## Architecture -## 5. Peer Support System +### Stack -### Cal.com Integration for 1:1s +- **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`) -**Setup:** +### Key Directories -- 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) +- `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` -**Matching System:** +### Domain Model -- 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 +Three membership **circles**: Community, Founder, Practitioner — each with different access and context. Five **contribution tiers**: $0, $5, $15, $30, $50/month via Helcim subscriptions. -## 6. Dashboard Design +Member statuses: `pending_payment`, `active`, `suspended`, `cancelled`. -### Personalized Sections +Events support ticketing with circle-specific pricing overrides and can be grouped into Series with bundled passes. -**Welcome Block:** +### Design System -- "Welcome back, [Name]" -- Your circle: [Circle] | Your contribution: $X/month -- Quick stats: Days as member, events attended, peers met +- **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 -**Community Pulse:** +### Environment -- Recent member updates (mini blog posts) -- Upcoming events this week -- New resources added -- New members to welcome +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. -**Your Activity:** +## Conventions -- Your upcoming events -- Scheduled peer sessions -- Recent discussions you're in -- Resources you've bookmarked +- 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 -**Take Action:** +## Product Spec -- Post an update -- Propose an event -- Book a peer session -- Browse resources -- Update profile +The sections below describe planned and in-progress features for reference. -**Impact Metrics:** +### 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 -- Total solidarity spots funded -- Events hosted this month -- Active members this week -- Resources shared +### Events System +- RSVP with capacity limits and waitlist management +- Calendar export (.ics), ticketing, series passes +- Member-proposed events with interest threshold -## 7. Collaborative Tools +### Resources (Planned) +- Learning paths by circle, templates and tools, case studies +- Tag by circle relevance, download tracking, version control -### 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 +### 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 diff --git a/HELCIM_PAYMENT_FIX.md b/HELCIM_PAYMENT_FIX.md deleted file mode 100644 index c1850b3..0000000 --- a/HELCIM_PAYMENT_FIX.md +++ /dev/null @@ -1,149 +0,0 @@ -# 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 deleted file mode 100644 index d1534e8..0000000 --- a/HELCIM_TEST_INSTRUCTIONS.md +++ /dev/null @@ -1,120 +0,0 @@ -# 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 deleted file mode 100644 index 265e02c..0000000 --- a/HELCIM_TICKET_INTEGRATION.md +++ /dev/null @@ -1,284 +0,0 @@ -# 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 deleted file mode 100644 index f16d76d..0000000 --- a/README.md +++ /dev/null @@ -1,75 +0,0 @@ -# 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 deleted file mode 100644 index 4697ace..0000000 --- a/SERIES_TICKETING_IMPLEMENTATION.md +++ /dev/null @@ -1,400 +0,0 @@ -# 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 deleted file mode 100644 index 5c7d439..0000000 --- a/UPDATE_SUMMARY.md +++ /dev/null @@ -1,57 +0,0 @@ -# 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 7b45bff..60b0d47 100644 --- a/app/app.config.ts +++ b/app/app.config.ts @@ -1,7 +1,7 @@ export default defineAppConfig({ ui: { colors: { - primary: "emerald", + primary: "amber", neutral: "stone", }, formField: { diff --git a/app/assets/css/fonts.css b/app/assets/css/fonts.css index fbcd1c2..bb94cbf 100644 --- a/app/assets/css/fonts.css +++ b/app/assets/css/fonts.css @@ -1,2 +1,43 @@ -/* Font declarations are now handled by @nuxt/fonts module */ -/* See nuxt.config.ts for font configuration */ \ No newline at end of file +/* + * 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; +} diff --git a/app/assets/css/main.css b/app/assets/css/main.css index 37d236e..5e7c6d3 100644 --- a/app/assets/css/main.css +++ b/app/assets/css/main.css @@ -1,3 +1,43 @@ +/* + * 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"; @@ -9,132 +49,229 @@ --font-sans: "Inter", sans-serif; --font-body: "Inter", sans-serif; --font-mono: "Ubuntu Mono", monospace; - --font-display: "NB Television Pro", monospace; + --font-display: "Quietism", serif; + --font-serif: "Quietism", serif; - /* 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; + /* 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; - /* 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; + /* 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; - /* 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; + /* 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; } .dark { - /* 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; + /* 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; - /* 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; + /* 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; - /* 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; + /* 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; } -/* Global ethereal background - light mode */ +/* Circle-specific tokens */ :root { - --ethereal-bg: + /* 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: radial-gradient( circle at 20% 80%, - rgba(40, 40, 40, 0.03) 0%, + rgba(184, 135, 58, 0.03) 0%, transparent 50% ), radial-gradient( circle at 80% 20%, - rgba(40, 40, 40, 0.02) 0%, + rgba(178, 104, 64, 0.02) 0%, transparent 50% ), radial-gradient( circle at 40% 40%, - rgba(40, 40, 40, 0.01) 0%, + rgba(138, 118, 88, 0.01) 0%, transparent 50% ); --halftone-pattern: radial-gradient( circle, - rgba(0, 0, 0, 0.1) 1px, + rgba(42, 36, 28, 0.1) 1px, transparent 1px ); --halftone-size: 8px 8px; } -/* Dark mode background */ +/* Dark mode */ .dark:root { - --ethereal-bg: + --ambient-bg: radial-gradient( circle at 20% 80%, - rgba(232, 232, 232, 0.03) 0%, + rgba(224, 184, 110, 0.03) 0%, transparent 50% ), radial-gradient( circle at 80% 20%, - rgba(232, 232, 232, 0.02) 0%, + rgba(218, 154, 114, 0.02) 0%, transparent 50% ), radial-gradient( circle at 40% 40%, - rgba(232, 232, 232, 0.01) 0%, + rgba(183, 164, 135, 0.01) 0%, transparent 50% ); --halftone-pattern: radial-gradient( circle, - rgba(255, 255, 255, 0.1) 1px, + rgba(240, 235, 228, 0.1) 1px, transparent 1px ); } @@ -144,12 +281,41 @@ html { } body { - background: var(--ethereal-bg), #f0f0f0; + background: var(--ambient-bg), #f0ebe4; background-attachment: fixed; } .dark body { - background: var(--ethereal-bg), #0a0a0a; + 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); } /* Halftone texture overlay */ @@ -170,106 +336,93 @@ body { pointer-events: none; } -/* Sparkle effects */ -@keyframes sparkle { - 0%, - 100% { - opacity: 0.3; - transform: scale(0.8); - } - 50% { - opacity: 1; - transform: scale(1.2); - } +/* Craft/Material Effects */ + +/* Warm box-shadow (replaces .ethereal-glow) */ +.candlelight-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); } -@keyframes twinkle { - 0%, - 100% { - opacity: 0.2; - } - 25% { - opacity: 0.8; - } - 75% { - opacity: 0.4; - } +/* Warm text-shadow (replaces .ethereal-text) */ +.warm-text { + text-shadow: 0 0 10px rgba(224, 184, 110, 0.25); } -.sparkle-field { +/* Subtle noise overlay */ +.ink-grain { position: relative; - overflow: hidden; } -.sparkle-field::after { +.ink-grain::before { 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; + background-image: url("/textures/grain.png"); + background-repeat: repeat; + background-size: 128px 128px; + mix-blend-mode: overlay; + opacity: 0.04; pointer-events: none; - opacity: 0.6; } -/* Ethereal glow effects */ -.ethereal-glow { - box-shadow: - 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); +/* Paper fiber overlay */ +.paper-texture { + position: relative; } -.ethereal-text { - text-shadow: 0 0 10px rgba(232, 232, 232, 0.3); +.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); } /* Dithered gradients */ .dithered-bg { background: - 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%); + 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%); background-size: 4px 4px; background-position: 0 0, @@ -278,6 +431,58 @@ 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 38b2f4e..8741d49 100644 --- a/app/components/AppFooter.vue +++ b/app/components/AppFooter.vue @@ -7,7 +7,7 @@ >
-

+

© {{ currentYear }} Ghost Guild

@@ -16,7 +16,7 @@
Contact diff --git a/app/components/AppNavigation.vue b/app/components/AppNavigation.vue index 555ebaf..bdbb47d 100644 --- a/app/components/AppNavigation.vue +++ b/app/components/AppNavigation.vue @@ -3,13 +3,13 @@ :class="[ isMobile ? 'w-full flex flex-col bg-transparent' - : 'w-64 lg:w-80 backdrop-blur-sm h-screen sticky top-0 flex flex-col bg-ghost-900 border-r border-ghost-700', + : 'w-64 lg:w-80 backdrop-blur-sm h-screen sticky top-0 flex flex-col bg-guild-900 border-r border-guild-700', ]" > -
+
- Ghost Guild Logo @@ -31,8 +31,8 @@ {{ item.label }} @@ -41,12 +41,12 @@ -
-

+

+

Contact us: hello@ghostguild.org

@@ -61,30 +61,30 @@
- {{ + {{ memberData?.name || "Member" }} Dashboard
-

+

Enter your email to receive a login link

diff --git a/app/components/EventSeriesBadge.vue b/app/components/EventSeriesBadge.vue index ed5a512..a8b23a0 100644 --- a/app/components/EventSeriesBadge.vue +++ b/app/components/EventSeriesBadge.vue @@ -1,18 +1,18 @@