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.
This commit is contained in:
parent
d588c49946
commit
a62e167876
39 changed files with 1300 additions and 2087 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -22,5 +22,5 @@ logs
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
/*.md
|
/docs/
|
||||||
scripts/*.js
|
scripts/*.js
|
||||||
|
|
|
||||||
367
CLAUDE.md
367
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
|
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.
|
||||||
- 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
|
|
||||||
|
|
||||||
**Privacy Controls:**
|
## Commands
|
||||||
|
|
||||||
- Public/members-only/private toggle per field
|
```bash
|
||||||
- Opt-in to member directory
|
npm run dev # Start dev server at http://localhost:3000
|
||||||
|
npm run build # Production build
|
||||||
### Member Updates/Mini Blog
|
npm run preview # Preview production build
|
||||||
|
|
||||||
- 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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Resource Features
|
No test framework is currently configured.
|
||||||
|
|
||||||
- Tag by circle relevance (but accessible to all)
|
## Architecture
|
||||||
- Download tracking for impact metrics
|
|
||||||
- Version control for templates
|
|
||||||
- Comment threads on resources
|
|
||||||
- "Request a resource" feature
|
|
||||||
|
|
||||||
## 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
|
- `app/composables/` — State management via `useState()` (no Pinia/Vuex). Key composables: `useAuth`, `useHelcim`, `useMemberPayment`, `useMemberStatus`
|
||||||
- Set their own hours/frequency
|
- `app/config/` — Circle definitions (`circles.js`) and contribution tiers (`contributions.js`) used across frontend and forms
|
||||||
- Cal.com handles scheduling
|
- `app/middleware/` — Route guards: `auth.js` (member pages), `admin.js` (admin pages), `coming-soon.global.js` (launch gate)
|
||||||
- Types of sessions:
|
- `app/layouts/` — `default`, `admin`, `landing`, `coming-soon`
|
||||||
- Peer support (30 min)
|
- `server/api/` — Nitro API routes organized by feature: `auth/`, `events/`, `members/`, `helcim/`, `series/`, `updates/`, `admin/`, `slack/`
|
||||||
- Co-founder check-in (45 min)
|
- `server/models/` — Mongoose schemas: `Member`, `Event`, `Series`, `Update`
|
||||||
- Practitioner office hours (60 min)
|
- `server/utils/` — Service integrations: `mongoose.js`, `helcim.js`, `resend.js`, `slack.ts`, `tickets.js`
|
||||||
|
|
||||||
**Matching System:**
|
### Domain Model
|
||||||
|
|
||||||
- Simple questionnaire about current needs
|
Three membership **circles**: Community, Founder, Practitioner — each with different access and context. Five **contribution tiers**: $0, $5, $15, $30, $50/month via Helcim subscriptions.
|
||||||
- Suggest 3 potential peers based on:
|
|
||||||
- Complementary skills/needs
|
|
||||||
- Time zone compatibility
|
|
||||||
- Circle alignment (optional)
|
|
||||||
- Book directly via Cal.com links
|
|
||||||
|
|
||||||
## 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]"
|
- **Colors:** `guild-*` (warm neutral), `candlelight-*` (amber/gold accent), `parchment-*` (cream surfaces), `ember-*` (rust accent), `earth-*` (brown/ochre) — defined in `app/assets/css/main.css`
|
||||||
- Your circle: [Circle] | Your contribution: $X/month
|
- **Circle tokens:** `--color-circle-community`, `--color-circle-founder`, `--color-circle-practitioner` with `-light`, `-dark`, `-bg` variants
|
||||||
- Quick stats: Days as member, events attended, peers met
|
- **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)
|
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.
|
||||||
- Upcoming events this week
|
|
||||||
- New resources added
|
|
||||||
- New members to welcome
|
|
||||||
|
|
||||||
**Your Activity:**
|
## Conventions
|
||||||
|
|
||||||
- Your upcoming events
|
- All frontend code is plain JavaScript (not TypeScript), using Vue 3 Composition API
|
||||||
- Scheduled peer sessions
|
- Server utilities auto-imported by Nitro — no explicit imports needed in API routes
|
||||||
- Recent discussions you're in
|
- Use `USwitch` (not `UToggle`) — this is the correct Nuxt UI 3+ component name
|
||||||
- Resources you've bookmarked
|
- 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
|
The sections below describe planned and in-progress features for reference.
|
||||||
- Propose an event
|
|
||||||
- Book a peer session
|
|
||||||
- Browse resources
|
|
||||||
- Update profile
|
|
||||||
|
|
||||||
**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 System
|
||||||
- Events hosted this month
|
- RSVP with capacity limits and waitlist management
|
||||||
- Active members this week
|
- Calendar export (.ics), ticketing, series passes
|
||||||
- Resources shared
|
- 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
|
### Implementation Priority
|
||||||
|
**Must have:** Payment processing, Slack automation, member dashboard, resource library, event listing/RSVP
|
||||||
**Use Cases:**
|
**Nice to have:** Member profiles, peer matching, Cal.com, member updates
|
||||||
|
**Post-launch:** Etherpad integration, member-proposed events, advanced search, analytics dashboard
|
||||||
- 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
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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)
|
|
||||||
75
README.md
75
README.md
|
|
@ -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.
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
export default defineAppConfig({
|
export default defineAppConfig({
|
||||||
ui: {
|
ui: {
|
||||||
colors: {
|
colors: {
|
||||||
primary: "emerald",
|
primary: "amber",
|
||||||
neutral: "stone",
|
neutral: "stone",
|
||||||
},
|
},
|
||||||
formField: {
|
formField: {
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,43 @@
|
||||||
/* Font declarations are now handled by @nuxt/fonts module */
|
/*
|
||||||
/* See nuxt.config.ts for font configuration */
|
* 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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 "./fonts.css";
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@import "@nuxt/ui";
|
@import "@nuxt/ui";
|
||||||
|
|
@ -9,132 +49,229 @@
|
||||||
--font-sans: "Inter", sans-serif;
|
--font-sans: "Inter", sans-serif;
|
||||||
--font-body: "Inter", sans-serif;
|
--font-body: "Inter", sans-serif;
|
||||||
--font-mono: "Ubuntu Mono", monospace;
|
--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) */
|
/* Guild - warm neutral ground (light mode: dark values for text on light bg) */
|
||||||
--color-ghost-50: #0a0a0a;
|
--color-guild-50: #1a1510;
|
||||||
--color-ghost-100: #1a1a1a;
|
--color-guild-100: #2a241c;
|
||||||
--color-ghost-200: #2a2a2a;
|
--color-guild-200: #3a332a;
|
||||||
--color-ghost-300: #3a3a3a;
|
--color-guild-300: #524939;
|
||||||
--color-ghost-400: #4a4a4a;
|
--color-guild-400: #6b5f4d;
|
||||||
--color-ghost-500: #6a6a6a;
|
--color-guild-500: #8a7c68;
|
||||||
--color-ghost-600: #8a8a8a;
|
--color-guild-600: #a49585;
|
||||||
--color-ghost-700: #b0b0b0;
|
--color-guild-700: #bfb3a2;
|
||||||
--color-ghost-800: #d0d0d0;
|
--color-guild-800: #d9d0c3;
|
||||||
--color-ghost-900: #f0f0f0;
|
--color-guild-900: #f0ebe4;
|
||||||
|
--color-guild-950: #f8f5f0;
|
||||||
|
|
||||||
/* Subtle accent - barely visible blue-gray (light mode) */
|
/* Candlelight - primary warm accent (amber/gold/ochre) */
|
||||||
--color-whisper-50: #0f1419;
|
--color-candlelight-50: #1f1708;
|
||||||
--color-whisper-100: #1a1f2e;
|
--color-candlelight-100: #3d2c0f;
|
||||||
--color-whisper-200: #252d40;
|
--color-candlelight-200: #5c4118;
|
||||||
--color-whisper-300: #2f3b52;
|
--color-candlelight-300: #7b5822;
|
||||||
--color-whisper-400: #3a4964;
|
--color-candlelight-400: #9a6f2c;
|
||||||
--color-whisper-500: #4f5d7a;
|
--color-candlelight-500: #b8873a;
|
||||||
--color-whisper-600: #687291;
|
--color-candlelight-600: #d09e4e;
|
||||||
--color-whisper-700: #8491a8;
|
--color-candlelight-700: #e0b86e;
|
||||||
--color-whisper-800: #a8b3c7;
|
--color-candlelight-800: #ecd09a;
|
||||||
--color-whisper-900: #d4dae6;
|
--color-candlelight-900: #f5e6c5;
|
||||||
|
--color-candlelight-950: #faf2e0;
|
||||||
|
|
||||||
/* Sparkle accent (light mode) */
|
/* Parchment - light content surfaces (cream/vellum) */
|
||||||
--color-sparkle-50: #202020;
|
--color-parchment-50: #1d1a14;
|
||||||
--color-sparkle-100: #404040;
|
--color-parchment-100: #332e24;
|
||||||
--color-sparkle-200: #606060;
|
--color-parchment-200: #4a4234;
|
||||||
--color-sparkle-300: #808080;
|
--color-parchment-300: #635844;
|
||||||
--color-sparkle-400: #a0a0a0;
|
--color-parchment-400: #7d6e56;
|
||||||
--color-sparkle-500: #c0c0c0;
|
--color-parchment-500: #97866c;
|
||||||
--color-sparkle-600: #d0d0d0;
|
--color-parchment-600: #afa088;
|
||||||
--color-sparkle-700: #e8e8e8;
|
--color-parchment-700: #c8baa5;
|
||||||
--color-sparkle-800: #f0f0f0;
|
--color-parchment-800: #ddd3c3;
|
||||||
--color-sparkle-900: #fafafa;
|
--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 {
|
.dark {
|
||||||
/* Ethereal color palette - dark mode (original values) */
|
/* Guild - warm neutral (dark mode: light values) */
|
||||||
--color-ghost-50: #f0f0f0;
|
--color-guild-50: #f0ebe4;
|
||||||
--color-ghost-100: #d0d0d0;
|
--color-guild-100: #d9d0c3;
|
||||||
--color-ghost-200: #b0b0b0;
|
--color-guild-200: #bfb3a2;
|
||||||
--color-ghost-300: #8a8a8a;
|
--color-guild-300: #a49585;
|
||||||
--color-ghost-400: #6a6a6a;
|
--color-guild-400: #8a7c68;
|
||||||
--color-ghost-500: #4a4a4a;
|
--color-guild-500: #6b5f4d;
|
||||||
--color-ghost-600: #3a3a3a;
|
--color-guild-600: #524939;
|
||||||
--color-ghost-700: #2a2a2a;
|
--color-guild-700: #3a332a;
|
||||||
--color-ghost-800: #1a1a1a;
|
--color-guild-800: #2a241c;
|
||||||
--color-ghost-900: #0a0a0a;
|
--color-guild-900: #1a1510;
|
||||||
|
--color-guild-950: #110e0a;
|
||||||
|
|
||||||
/* Subtle accent - barely visible blue-gray (dark mode) */
|
/* Candlelight - primary warm accent (dark mode) */
|
||||||
--color-whisper-50: #d4dae6;
|
--color-candlelight-50: #faf2e0;
|
||||||
--color-whisper-100: #a8b3c7;
|
--color-candlelight-100: #f5e6c5;
|
||||||
--color-whisper-200: #8491a8;
|
--color-candlelight-200: #ecd09a;
|
||||||
--color-whisper-300: #687291;
|
--color-candlelight-300: #e0b86e;
|
||||||
--color-whisper-400: #4f5d7a;
|
--color-candlelight-400: #d09e4e;
|
||||||
--color-whisper-500: #3a4964;
|
--color-candlelight-500: #b8873a;
|
||||||
--color-whisper-600: #2f3b52;
|
--color-candlelight-600: #9a6f2c;
|
||||||
--color-whisper-700: #252d40;
|
--color-candlelight-700: #7b5822;
|
||||||
--color-whisper-800: #1a1f2e;
|
--color-candlelight-800: #5c4118;
|
||||||
--color-whisper-900: #0f1419;
|
--color-candlelight-900: #3d2c0f;
|
||||||
|
--color-candlelight-950: #1f1708;
|
||||||
|
|
||||||
/* Sparkle accent (dark mode) */
|
/* Parchment - light content surfaces (dark mode) */
|
||||||
--color-sparkle-50: #fafafa;
|
--color-parchment-50: #f9f6f0;
|
||||||
--color-sparkle-100: #f0f0f0;
|
--color-parchment-100: #f0ebe0;
|
||||||
--color-sparkle-200: #e8e8e8;
|
--color-parchment-200: #ddd3c3;
|
||||||
--color-sparkle-300: #d0d0d0;
|
--color-parchment-300: #c8baa5;
|
||||||
--color-sparkle-400: #c0c0c0;
|
--color-parchment-400: #afa088;
|
||||||
--color-sparkle-500: #a0a0a0;
|
--color-parchment-500: #97866c;
|
||||||
--color-sparkle-600: #808080;
|
--color-parchment-600: #7d6e56;
|
||||||
--color-sparkle-700: #606060;
|
--color-parchment-700: #635844;
|
||||||
--color-sparkle-800: #404040;
|
--color-parchment-800: #4a4234;
|
||||||
--color-sparkle-900: #202020;
|
--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 {
|
: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(
|
radial-gradient(
|
||||||
circle at 20% 80%,
|
circle at 20% 80%,
|
||||||
rgba(40, 40, 40, 0.03) 0%,
|
rgba(184, 135, 58, 0.03) 0%,
|
||||||
transparent 50%
|
transparent 50%
|
||||||
),
|
),
|
||||||
radial-gradient(
|
radial-gradient(
|
||||||
circle at 80% 20%,
|
circle at 80% 20%,
|
||||||
rgba(40, 40, 40, 0.02) 0%,
|
rgba(178, 104, 64, 0.02) 0%,
|
||||||
transparent 50%
|
transparent 50%
|
||||||
),
|
),
|
||||||
radial-gradient(
|
radial-gradient(
|
||||||
circle at 40% 40%,
|
circle at 40% 40%,
|
||||||
rgba(40, 40, 40, 0.01) 0%,
|
rgba(138, 118, 88, 0.01) 0%,
|
||||||
transparent 50%
|
transparent 50%
|
||||||
);
|
);
|
||||||
|
|
||||||
--halftone-pattern: radial-gradient(
|
--halftone-pattern: radial-gradient(
|
||||||
circle,
|
circle,
|
||||||
rgba(0, 0, 0, 0.1) 1px,
|
rgba(42, 36, 28, 0.1) 1px,
|
||||||
transparent 1px
|
transparent 1px
|
||||||
);
|
);
|
||||||
--halftone-size: 8px 8px;
|
--halftone-size: 8px 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dark mode background */
|
/* Dark mode */
|
||||||
.dark:root {
|
.dark:root {
|
||||||
--ethereal-bg:
|
--ambient-bg:
|
||||||
radial-gradient(
|
radial-gradient(
|
||||||
circle at 20% 80%,
|
circle at 20% 80%,
|
||||||
rgba(232, 232, 232, 0.03) 0%,
|
rgba(224, 184, 110, 0.03) 0%,
|
||||||
transparent 50%
|
transparent 50%
|
||||||
),
|
),
|
||||||
radial-gradient(
|
radial-gradient(
|
||||||
circle at 80% 20%,
|
circle at 80% 20%,
|
||||||
rgba(232, 232, 232, 0.02) 0%,
|
rgba(218, 154, 114, 0.02) 0%,
|
||||||
transparent 50%
|
transparent 50%
|
||||||
),
|
),
|
||||||
radial-gradient(
|
radial-gradient(
|
||||||
circle at 40% 40%,
|
circle at 40% 40%,
|
||||||
rgba(232, 232, 232, 0.01) 0%,
|
rgba(183, 164, 135, 0.01) 0%,
|
||||||
transparent 50%
|
transparent 50%
|
||||||
);
|
);
|
||||||
|
|
||||||
--halftone-pattern: radial-gradient(
|
--halftone-pattern: radial-gradient(
|
||||||
circle,
|
circle,
|
||||||
rgba(255, 255, 255, 0.1) 1px,
|
rgba(240, 235, 228, 0.1) 1px,
|
||||||
transparent 1px
|
transparent 1px
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -144,12 +281,41 @@ html {
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: var(--ethereal-bg), #f0f0f0;
|
background: var(--ambient-bg), #f0ebe4;
|
||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark body {
|
.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 */
|
/* Halftone texture overlay */
|
||||||
|
|
@ -170,106 +336,93 @@ body {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sparkle effects */
|
/* Craft/Material Effects */
|
||||||
@keyframes sparkle {
|
|
||||||
0%,
|
/* Warm box-shadow (replaces .ethereal-glow) */
|
||||||
100% {
|
.candlelight-glow {
|
||||||
opacity: 0.3;
|
box-shadow:
|
||||||
transform: scale(0.8);
|
0 0 20px rgba(184, 135, 58, 0.12),
|
||||||
}
|
0 0 40px rgba(184, 135, 58, 0.06),
|
||||||
50% {
|
inset 0 1px 0 rgba(240, 235, 228, 0.08);
|
||||||
opacity: 1;
|
|
||||||
transform: scale(1.2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes twinkle {
|
/* Warm text-shadow (replaces .ethereal-text) */
|
||||||
0%,
|
.warm-text {
|
||||||
100% {
|
text-shadow: 0 0 10px rgba(224, 184, 110, 0.25);
|
||||||
opacity: 0.2;
|
|
||||||
}
|
|
||||||
25% {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
75% {
|
|
||||||
opacity: 0.4;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sparkle-field {
|
/* Subtle noise overlay */
|
||||||
|
.ink-grain {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sparkle-field::after {
|
.ink-grain::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background-image:
|
background-image: url("/textures/grain.png");
|
||||||
radial-gradient(
|
background-repeat: repeat;
|
||||||
circle at 10% 20%,
|
background-size: 128px 128px;
|
||||||
var(--color-sparkle-200) 1px,
|
mix-blend-mode: overlay;
|
||||||
transparent 1px
|
opacity: 0.04;
|
||||||
),
|
|
||||||
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;
|
pointer-events: none;
|
||||||
opacity: 0.6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ethereal glow effects */
|
/* Paper fiber overlay */
|
||||||
.ethereal-glow {
|
.paper-texture {
|
||||||
box-shadow:
|
position: relative;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ethereal-text {
|
.paper-texture::before {
|
||||||
text-shadow: 0 0 10px rgba(232, 232, 232, 0.3);
|
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 gradients */
|
||||||
.dithered-bg {
|
.dithered-bg {
|
||||||
background:
|
background:
|
||||||
linear-gradient(45deg, var(--color-ghost-800) 25%, transparent 25%),
|
linear-gradient(45deg, var(--color-guild-800) 25%, transparent 25%),
|
||||||
linear-gradient(-45deg, var(--color-ghost-800) 25%, transparent 25%),
|
linear-gradient(-45deg, var(--color-guild-800) 25%, transparent 25%),
|
||||||
linear-gradient(45deg, transparent 75%, var(--color-ghost-700) 75%),
|
linear-gradient(45deg, transparent 75%, var(--color-guild-700) 75%),
|
||||||
linear-gradient(-45deg, transparent 75%, var(--color-ghost-700) 75%);
|
linear-gradient(-45deg, transparent 75%, var(--color-guild-700) 75%);
|
||||||
background-size: 4px 4px;
|
background-size: 4px 4px;
|
||||||
background-position:
|
background-position:
|
||||||
0 0,
|
0 0,
|
||||||
|
|
@ -278,6 +431,58 @@ body {
|
||||||
-2px 0px;
|
-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 */
|
/* Mobile responsive utilities */
|
||||||
@media (max-width: 1023px) {
|
@media (max-width: 1023px) {
|
||||||
/* Prevent horizontal scroll on mobile */
|
/* Prevent horizontal scroll on mobile */
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
>
|
>
|
||||||
<!-- Left: Copyright and minimal info -->
|
<!-- Left: Copyright and minimal info -->
|
||||||
<div>
|
<div>
|
||||||
<p class="text-ghost-500 text-xs mb-2">
|
<p class="text-guild-500 text-xs mb-2">
|
||||||
© {{ currentYear }} Ghost Guild
|
© {{ currentYear }} Ghost Guild
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
<div class="flex flex-wrap gap-6 text-xs">
|
<div class="flex flex-wrap gap-6 text-xs">
|
||||||
<a
|
<a
|
||||||
href="mailto:hello@ghostguild.org"
|
href="mailto:hello@ghostguild.org"
|
||||||
class="text-ghost-500 hover:text-ghost-300 transition-colors"
|
class="text-guild-500 hover:text-guild-300 transition-colors"
|
||||||
>
|
>
|
||||||
Contact
|
Contact
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@
|
||||||
:class="[
|
:class="[
|
||||||
isMobile
|
isMobile
|
||||||
? 'w-full flex flex-col bg-transparent'
|
? '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',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<!-- Logo/Brand at top (desktop only) -->
|
<!-- Logo/Brand at top (desktop only) -->
|
||||||
<div v-if="!isMobile" class="p-8 border-b border-ghost-700 bg-primary-500">
|
<div v-if="!isMobile" class="p-8 border-b border-guild-700 bg-primary-500">
|
||||||
<NuxtLink to="/" class="flex flex-col items-center gap-3 group">
|
<NuxtLink to="/" class="flex flex-col items-center gap-3 group">
|
||||||
<span class="text-xl font-bold text-white ethereal-text tracking-wider"
|
<span class="text-xl font-bold text-white warm-text tracking-wider"
|
||||||
>Ghost Guild Logo</span
|
>Ghost Guild Logo</span
|
||||||
>
|
>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
@ -31,8 +31,8 @@
|
||||||
<!-- Hover indicator -->
|
<!-- Hover indicator -->
|
||||||
|
|
||||||
<span
|
<span
|
||||||
class="text-ghost-200 hover:text-ghost-100 transition-all duration-300 text-lg tracking-wide block py-2 hover:ethereal-text"
|
class="text-guild-200 hover:text-guild-100 transition-all duration-300 text-lg tracking-wide block py-2 hover:warm-text"
|
||||||
active-class="text-ghost-100 ethereal-text translate-x-2"
|
active-class="text-guild-100 warm-text translate-x-2"
|
||||||
>
|
>
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -41,12 +41,12 @@
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<!-- Contact Email -->
|
<!-- Contact Email -->
|
||||||
<div class="mt-8 pt-6 border-t border-ghost-800/50">
|
<div class="mt-8 pt-6 border-t border-guild-800/50">
|
||||||
<p class="text-ghost-500 text-xs">
|
<p class="text-guild-500 text-xs">
|
||||||
Contact us:
|
Contact us:
|
||||||
<a
|
<a
|
||||||
href="mailto:hello@ghostguild.org"
|
href="mailto:hello@ghostguild.org"
|
||||||
class="text-ghost-400 hover:text-ghost-300 transition-colors"
|
class="text-guild-400 hover:text-guild-300 transition-colors"
|
||||||
>hello@ghostguild.org</a
|
>hello@ghostguild.org</a
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -61,30 +61,30 @@
|
||||||
<div
|
<div
|
||||||
:class="
|
:class="
|
||||||
isMobile
|
isMobile
|
||||||
? 'mt-8 pt-6 border-t border-ghost-800/50'
|
? 'mt-8 pt-6 border-t border-guild-800/50'
|
||||||
: 'mt-12 pt-8 border-t border-ghost-800/50'
|
: 'mt-12 pt-8 border-t border-guild-800/50'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<div v-if="isAuthenticated" class="space-y-4">
|
<div v-if="isAuthenticated" class="space-y-4">
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
to="/member/dashboard"
|
to="/member/dashboard"
|
||||||
class="block text-ghost-300 hover:text-ghost-100 hover:ethereal-text transition-all duration-300 py-2"
|
class="block text-guild-300 hover:text-guild-100 hover:warm-text transition-all duration-300 py-2"
|
||||||
@click="handleNavigate"
|
@click="handleNavigate"
|
||||||
>
|
>
|
||||||
<span class="block text-sm text-whisper-400 mb-1">{{
|
<span class="block text-sm text-candlelight-400 mb-1">{{
|
||||||
memberData?.name || "Member"
|
memberData?.name || "Member"
|
||||||
}}</span>
|
}}</span>
|
||||||
Dashboard
|
Dashboard
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<button
|
<button
|
||||||
@click="handleLogout"
|
@click="handleLogout"
|
||||||
class="text-ghost-500 hover:text-ghost-300 transition-all duration-300 text-sm"
|
class="text-guild-500 hover:text-guild-300 transition-all duration-300 text-sm"
|
||||||
>
|
>
|
||||||
Logout
|
Logout
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="space-y-4">
|
<div v-else class="space-y-4">
|
||||||
<p class="text-ghost-400 text-sm mb-4">
|
<p class="text-guild-400 text-sm mb-4">
|
||||||
Enter your email to receive a login link
|
Enter your email to receive a login link
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="series-badge p-4 bg-ghost-800/50 dark:bg-ghost-700/30 rounded-xl border border-ghost-600 dark:border-ghost-600"
|
class="series-badge p-4 bg-guild-800/50 dark:bg-guild-700/30 rounded-xl border border-guild-600 dark:border-guild-600"
|
||||||
>
|
>
|
||||||
<div class="flex items-start justify-between gap-6">
|
<div class="flex items-start justify-between gap-6">
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<div class="flex flex-wrap items-center gap-2 mb-2">
|
<div class="flex flex-wrap items-center gap-2 mb-2">
|
||||||
<span
|
<span
|
||||||
class="series-badge__label text-sm font-semibold text-ghost-300 dark:text-ghost-300"
|
class="series-badge__label text-sm font-semibold text-guild-300 dark:text-guild-300"
|
||||||
>
|
>
|
||||||
Part of a Series
|
Part of a Series
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="totalEvents"
|
v-if="totalEvents"
|
||||||
class="series-badge__count inline-flex items-center px-2 py-0.5 rounded-md bg-ghost-700/50 dark:bg-ghost-600/50 text-sm font-medium text-ghost-200 dark:text-ghost-200"
|
class="series-badge__count inline-flex items-center px-2 py-0.5 rounded-md bg-guild-700/50 dark:bg-guild-600/50 text-sm font-medium text-guild-200 dark:text-guild-200"
|
||||||
>
|
>
|
||||||
<template v-if="position">
|
<template v-if="position">
|
||||||
Event {{ position }} of {{ totalEvents }}
|
Event {{ position }} of {{ totalEvents }}
|
||||||
|
|
@ -21,13 +21,13 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<h3
|
<h3
|
||||||
class="series-badge__title text-lg font-semibold text-ghost-100 dark:text-ghost-100 mb-2"
|
class="series-badge__title text-lg font-semibold text-guild-100 dark:text-guild-100 mb-2"
|
||||||
>
|
>
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</h3>
|
</h3>
|
||||||
<p
|
<p
|
||||||
v-if="description"
|
v-if="description"
|
||||||
class="series-badge__description text-sm text-ghost-300 dark:text-ghost-300"
|
class="series-badge__description text-sm text-guild-300 dark:text-guild-300"
|
||||||
>
|
>
|
||||||
{{ description }}
|
{{ description }}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="series-ticket-card border border-ghost-600 dark:border-ghost-600 rounded-xl overflow-hidden"
|
class="series-ticket-card border border-guild-600 dark:border-guild-600 rounded-xl overflow-hidden"
|
||||||
>
|
>
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div
|
<div
|
||||||
|
|
@ -39,27 +39,27 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Body -->
|
<!-- Body -->
|
||||||
<div class="p-6 bg-ghost-800/50 dark:bg-ghost-700/30">
|
<div class="p-6 bg-guild-800/50 dark:bg-guild-700/30">
|
||||||
<!-- What's Included -->
|
<!-- What's Included -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h4 class="text-sm font-semibold text-ghost-200 dark:text-ghost-200 mb-3 uppercase tracking-wide">
|
<h4 class="text-sm font-semibold text-guild-200 dark:text-guild-200 mb-3 uppercase tracking-wide">
|
||||||
What's Included
|
What's Included
|
||||||
</h4>
|
</h4>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div class="flex items-center gap-2 text-ghost-300 dark:text-ghost-300">
|
<div class="flex items-center gap-2 text-guild-300 dark:text-guild-300">
|
||||||
<Icon name="heroicons:check-circle" class="w-5 h-5 text-green-400" />
|
<Icon name="heroicons:check-circle" class="w-5 h-5 text-green-400" />
|
||||||
<span>Access to all {{ totalEvents }} events in the series</span>
|
<span>Access to all {{ totalEvents }} events in the series</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="ticket.isFree && !isMember"
|
v-if="ticket.isFree && !isMember"
|
||||||
class="flex items-center gap-2 text-ghost-300 dark:text-ghost-300"
|
class="flex items-center gap-2 text-guild-300 dark:text-guild-300"
|
||||||
>
|
>
|
||||||
<Icon name="heroicons:check-circle" class="w-5 h-5 text-green-400" />
|
<Icon name="heroicons:check-circle" class="w-5 h-5 text-green-400" />
|
||||||
<span>Automatic registration for all sessions</span>
|
<span>Automatic registration for all sessions</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="memberSavings > 0"
|
v-if="memberSavings > 0"
|
||||||
class="flex items-center gap-2 text-ghost-300 dark:text-ghost-300"
|
class="flex items-center gap-2 text-guild-300 dark:text-guild-300"
|
||||||
>
|
>
|
||||||
<Icon name="heroicons:check-circle" class="w-5 h-5 text-green-400" />
|
<Icon name="heroicons:check-circle" class="w-5 h-5 text-green-400" />
|
||||||
<span>Save {{ formatPrice(memberSavings, ticket.currency) }} as a member</span>
|
<span>Save {{ formatPrice(memberSavings, ticket.currency) }} as a member</span>
|
||||||
|
|
@ -69,14 +69,14 @@
|
||||||
|
|
||||||
<!-- Events List Preview -->
|
<!-- Events List Preview -->
|
||||||
<div v-if="events && events.length > 0" class="mb-6">
|
<div v-if="events && events.length > 0" class="mb-6">
|
||||||
<h4 class="text-sm font-semibold text-ghost-200 dark:text-ghost-200 mb-3 uppercase tracking-wide">
|
<h4 class="text-sm font-semibold text-guild-200 dark:text-guild-200 mb-3 uppercase tracking-wide">
|
||||||
Series Schedule
|
Series Schedule
|
||||||
</h4>
|
</h4>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div
|
<div
|
||||||
v-for="(event, index) in events.slice(0, 3)"
|
v-for="(event, index) in events.slice(0, 3)"
|
||||||
:key="event.id"
|
:key="event.id"
|
||||||
class="flex items-start gap-3 p-3 bg-ghost-700/50 dark:bg-ghost-600/30 rounded-lg"
|
class="flex items-start gap-3 p-3 bg-guild-700/50 dark:bg-guild-600/30 rounded-lg"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="w-8 h-8 rounded-full bg-purple-600/20 border border-purple-500/30 flex items-center justify-center flex-shrink-0"
|
class="w-8 h-8 rounded-full bg-purple-600/20 border border-purple-500/30 flex items-center justify-center flex-shrink-0"
|
||||||
|
|
@ -84,17 +84,17 @@
|
||||||
<span class="text-sm font-bold text-purple-300">{{ index + 1 }}</span>
|
<span class="text-sm font-bold text-purple-300">{{ index + 1 }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<div class="font-medium text-ghost-100 dark:text-ghost-100 text-sm">
|
<div class="font-medium text-guild-100 dark:text-guild-100 text-sm">
|
||||||
{{ event.title }}
|
{{ event.title }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-ghost-400 dark:text-ghost-400 mt-1">
|
<div class="text-xs text-guild-400 dark:text-guild-400 mt-1">
|
||||||
{{ formatEventDate(event.startDate) }}
|
{{ formatEventDate(event.startDate) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="events.length > 3"
|
v-if="events.length > 3"
|
||||||
class="text-center text-sm text-ghost-400 dark:text-ghost-400 pt-2"
|
class="text-center text-sm text-guild-400 dark:text-guild-400 pt-2"
|
||||||
>
|
>
|
||||||
+ {{ events.length - 3 }} more event{{ events.length - 3 !== 1 ? 's' : '' }}
|
+ {{ events.length - 3 }} more event{{ events.length - 3 !== 1 ? 's' : '' }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
:class="[
|
:class="[
|
||||||
isSelected
|
isSelected
|
||||||
? 'border-primary bg-primary/5'
|
? 'border-primary bg-primary/5'
|
||||||
: 'border-ghost-600 bg-ghost-800/50',
|
: 'border-guild-600 bg-guild-800/50',
|
||||||
isAvailable && !alreadyRegistered
|
isAvailable && !alreadyRegistered
|
||||||
? 'hover:border-primary/50 cursor-pointer'
|
? 'hover:border-primary/50 cursor-pointer'
|
||||||
: 'opacity-60 cursor-not-allowed',
|
: 'opacity-60 cursor-not-allowed',
|
||||||
|
|
@ -14,10 +14,10 @@
|
||||||
<!-- Ticket Header -->
|
<!-- Ticket Header -->
|
||||||
<div class="flex items-start justify-between mb-4">
|
<div class="flex items-start justify-between mb-4">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-lg font-semibold text-ghost-100">
|
<h3 class="text-lg font-semibold text-guild-100">
|
||||||
{{ ticketInfo.name }}
|
{{ ticketInfo.name }}
|
||||||
</h3>
|
</h3>
|
||||||
<p v-if="ticketInfo.description" class="text-sm text-ghost-300 mt-1">
|
<p v-if="ticketInfo.description" class="text-sm text-guild-300 mt-1">
|
||||||
{{ ticketInfo.description }}
|
{{ ticketInfo.description }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -37,7 +37,7 @@
|
||||||
<div class="flex items-baseline gap-2">
|
<div class="flex items-baseline gap-2">
|
||||||
<span
|
<span
|
||||||
class="text-3xl font-bold"
|
class="text-3xl font-bold"
|
||||||
:class="ticketInfo.isFree ? 'text-green-400' : 'text-ghost-100'"
|
:class="ticketInfo.isFree ? 'text-green-400' : 'text-guild-100'"
|
||||||
>
|
>
|
||||||
{{ ticketInfo.formattedPrice }}
|
{{ ticketInfo.formattedPrice }}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -56,7 +56,7 @@
|
||||||
v-if="ticketInfo.isEarlyBird && ticketInfo.formattedRegularPrice"
|
v-if="ticketInfo.isEarlyBird && ticketInfo.formattedRegularPrice"
|
||||||
class="mt-1"
|
class="mt-1"
|
||||||
>
|
>
|
||||||
<span class="text-sm text-ghost-400 line-through">
|
<span class="text-sm text-guild-400 line-through">
|
||||||
Regular: {{ ticketInfo.formattedRegularPrice }}
|
Regular: {{ ticketInfo.formattedRegularPrice }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -80,7 +80,7 @@
|
||||||
<Icon name="heroicons:check-circle" class="w-4 h-4 inline mr-1" />
|
<Icon name="heroicons:check-circle" class="w-4 h-4 inline mr-1" />
|
||||||
You save {{ formatPrice(ticketInfo.memberSavings) }} as a member!
|
You save {{ formatPrice(ticketInfo.memberSavings) }} as a member!
|
||||||
</p>
|
</p>
|
||||||
<p class="text-xs text-ghost-400 mt-1">
|
<p class="text-xs text-guild-400 mt-1">
|
||||||
Public price: {{ ticketInfo.publicTicket.formattedPrice }}
|
Public price: {{ ticketInfo.publicTicket.formattedPrice }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -102,10 +102,10 @@
|
||||||
<Icon name="heroicons:x-circle-solid" class="w-4 h-4" />
|
<Icon name="heroicons:x-circle-solid" class="w-4 h-4" />
|
||||||
Sold Out
|
Sold Out
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="ticketInfo.remaining !== null" class="text-ghost-300">
|
<span v-else-if="ticketInfo.remaining !== null" class="text-guild-300">
|
||||||
{{ ticketInfo.remaining }} remaining
|
{{ ticketInfo.remaining }} remaining
|
||||||
</span>
|
</span>
|
||||||
<span v-else class="text-ghost-300"> Unlimited availability </span>
|
<span v-else class="text-guild-300"> Unlimited availability </span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Selection Indicator -->
|
<!-- Selection Indicator -->
|
||||||
|
|
@ -117,7 +117,7 @@
|
||||||
<!-- Waitlist Option -->
|
<!-- Waitlist Option -->
|
||||||
<div
|
<div
|
||||||
v-if="!isAvailable && ticketInfo.waitlistAvailable && !alreadyRegistered"
|
v-if="!isAvailable && ticketInfo.waitlistAvailable && !alreadyRegistered"
|
||||||
class="mt-4 pt-4 border-t border-ghost-600"
|
class="mt-4 pt-4 border-t border-guild-600"
|
||||||
>
|
>
|
||||||
<UButton
|
<UButton
|
||||||
color="gray"
|
color="gray"
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<div
|
<div
|
||||||
class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"
|
class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"
|
||||||
></div>
|
></div>
|
||||||
<p class="text-ghost-300">Loading ticket information...</p>
|
<p class="text-guild-300">Loading ticket information...</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Error State -->
|
<!-- Error State -->
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
<strong>{{ ticketInfo.series?.title }}</strong> and requires a series
|
<strong>{{ ticketInfo.series?.title }}</strong> and requires a series
|
||||||
pass to attend.
|
pass to attend.
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-ghost-300 mb-6">
|
<p class="text-sm text-guild-300 mb-6">
|
||||||
Purchase a series pass to get access to all events in this series.
|
Purchase a series pass to get access to all events in this series.
|
||||||
</p>
|
</p>
|
||||||
<UButton
|
<UButton
|
||||||
|
|
@ -70,7 +70,7 @@
|
||||||
details.
|
details.
|
||||||
</template>
|
</template>
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-ghost-300">
|
<p class="text-sm text-guild-300">
|
||||||
See you on {{ formatEventDate(eventStartDate) }}!
|
See you on {{ formatEventDate(eventStartDate) }}!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -89,7 +89,7 @@
|
||||||
|
|
||||||
<!-- Registration Form -->
|
<!-- Registration Form -->
|
||||||
<div v-if="ticketInfo.available && !ticketInfo.alreadyRegistered">
|
<div v-if="ticketInfo.available && !ticketInfo.alreadyRegistered">
|
||||||
<h3 class="text-xl font-bold text-ghost-100 mb-4">
|
<h3 class="text-xl font-bold text-guild-100 mb-4">
|
||||||
{{ ticketInfo.isFree ? "Register" : "Purchase Ticket" }}
|
{{ ticketInfo.isFree ? "Register" : "Purchase Ticket" }}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
|
|
@ -98,7 +98,7 @@
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
for="name"
|
for="name"
|
||||||
class="block text-sm font-medium text-ghost-200 mb-2"
|
class="block text-sm font-medium text-guild-200 mb-2"
|
||||||
>
|
>
|
||||||
Full Name
|
Full Name
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -116,7 +116,7 @@
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
for="email"
|
for="email"
|
||||||
class="block text-sm font-medium text-ghost-200 mb-2"
|
class="block text-sm font-medium text-guild-200 mb-2"
|
||||||
>
|
>
|
||||||
Email Address
|
Email Address
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -128,7 +128,7 @@
|
||||||
placeholder="Enter your email"
|
placeholder="Enter your email"
|
||||||
:disabled="processing || isLoggedIn"
|
:disabled="processing || isLoggedIn"
|
||||||
/>
|
/>
|
||||||
<p v-if="isLoggedIn" class="text-xs text-ghost-400 mt-1">
|
<p v-if="isLoggedIn" class="text-xs text-guild-400 mt-1">
|
||||||
Using your member email
|
Using your member email
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -185,10 +185,10 @@
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
name="heroicons:ticket"
|
name="heroicons:ticket"
|
||||||
class="w-16 h-16 text-ghost-400 mx-auto mb-4"
|
class="w-16 h-16 text-guild-400 mx-auto mb-4"
|
||||||
/>
|
/>
|
||||||
<h3 class="text-xl font-bold text-ghost-100 mb-2">Event Sold Out</h3>
|
<h3 class="text-xl font-bold text-guild-100 mb-2">Event Sold Out</h3>
|
||||||
<p class="text-ghost-300 mb-6">
|
<p class="text-guild-300 mb-6">
|
||||||
This event is currently at capacity. Join the waitlist to be notified
|
This event is currently at capacity. Join the waitlist to be notified
|
||||||
if spots become available.
|
if spots become available.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -203,8 +203,8 @@
|
||||||
name="heroicons:x-circle"
|
name="heroicons:x-circle"
|
||||||
class="w-16 h-16 text-red-400 mx-auto mb-4"
|
class="w-16 h-16 text-red-400 mx-auto mb-4"
|
||||||
/>
|
/>
|
||||||
<h3 class="text-xl font-bold text-ghost-100 mb-2">Event Sold Out</h3>
|
<h3 class="text-xl font-bold text-guild-100 mb-2">Event Sold Out</h3>
|
||||||
<p class="text-ghost-300">
|
<p class="text-guild-300">
|
||||||
Unfortunately, this event is at capacity and no longer accepting
|
Unfortunately, this event is at capacity and no longer accepting
|
||||||
registrations.
|
registrations.
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,12 @@
|
||||||
:description="description"
|
:description="description"
|
||||||
:dismissible="dismissible"
|
:dismissible="dismissible"
|
||||||
:ui="{
|
:ui="{
|
||||||
content: 'bg-ghost-900 border border-ghost-700',
|
content: 'bg-guild-900 border border-guild-700',
|
||||||
header: 'bg-ghost-900 border-b border-ghost-700',
|
header: 'bg-guild-900 border-b border-guild-700',
|
||||||
body: 'bg-ghost-900',
|
body: 'bg-guild-900',
|
||||||
footer: 'bg-ghost-900 border-t border-ghost-700',
|
footer: 'bg-guild-900 border-t border-guild-700',
|
||||||
title: 'text-ghost-100',
|
title: 'text-guild-100',
|
||||||
description: 'text-ghost-400',
|
description: 'text-guild-400',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
|
|
@ -20,9 +20,9 @@
|
||||||
<div class="w-16 h-16 bg-green-500/20 rounded-full flex items-center justify-center mx-auto mb-4">
|
<div class="w-16 h-16 bg-green-500/20 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
<Icon name="heroicons:check-circle" class="w-10 h-10 text-green-400" />
|
<Icon name="heroicons:check-circle" class="w-10 h-10 text-green-400" />
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-semibold text-ghost-100 mb-2">Check your email</h3>
|
<h3 class="text-lg font-semibold text-guild-100 mb-2">Check your email</h3>
|
||||||
<p class="text-ghost-300">
|
<p class="text-guild-300">
|
||||||
We've sent a magic link to <strong class="text-ghost-100">{{ loginForm.email }}</strong>.
|
We've sent a magic link to <strong class="text-guild-100">{{ loginForm.email }}</strong>.
|
||||||
Click the link to sign in.
|
Click the link to sign in.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -37,16 +37,16 @@
|
||||||
class="w-full"
|
class="w-full"
|
||||||
placeholder="your.email@example.com"
|
placeholder="your.email@example.com"
|
||||||
:ui="{
|
:ui="{
|
||||||
root: 'bg-ghost-800 border-ghost-600 text-ghost-100 placeholder-ghost-500',
|
root: 'bg-guild-800 border-guild-600 text-guild-100 placeholder-guild-500',
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
|
||||||
<!-- Info Box -->
|
<!-- Info Box -->
|
||||||
<div class="bg-ghost-800 border border-ghost-600 p-4 rounded-lg mb-6">
|
<div class="bg-guild-800 border border-guild-600 p-4 rounded-lg mb-6">
|
||||||
<div class="flex items-start gap-3">
|
<div class="flex items-start gap-3">
|
||||||
<Icon name="heroicons:envelope" class="w-5 h-5 text-whisper-400 flex-shrink-0 mt-0.5" />
|
<Icon name="heroicons:envelope" class="w-5 h-5 text-candlelight-400 flex-shrink-0 mt-0.5" />
|
||||||
<p class="text-sm text-ghost-300">
|
<p class="text-sm text-guild-300">
|
||||||
We'll send you a secure magic link. No password needed!
|
We'll send you a secure magic link. No password needed!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -73,12 +73,12 @@
|
||||||
</UForm>
|
</UForm>
|
||||||
|
|
||||||
<!-- Join Link -->
|
<!-- Join Link -->
|
||||||
<div v-if="!loginSuccess" class="text-center pt-2 border-t border-ghost-700">
|
<div v-if="!loginSuccess" class="text-center pt-2 border-t border-guild-700">
|
||||||
<p class="text-ghost-400 text-sm pt-4">
|
<p class="text-guild-400 text-sm pt-4">
|
||||||
Don't have an account?
|
Don't have an account?
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
to="/join"
|
to="/join"
|
||||||
class="text-whisper-400 hover:text-whisper-300 font-medium"
|
class="text-candlelight-400 hover:text-candlelight-300 font-medium"
|
||||||
@click="close"
|
@click="close"
|
||||||
>
|
>
|
||||||
Join Ghost Guild
|
Join Ghost Guild
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@
|
||||||
<button
|
<button
|
||||||
v-if="dismissible"
|
v-if="dismissible"
|
||||||
@click="isDismissed = true"
|
@click="isDismissed = true"
|
||||||
class="text-ghost-400 hover:text-ghost-200 transition-colors"
|
class="text-guild-400 hover:text-guild-200 transition-colors"
|
||||||
:aria-label="`Dismiss ${statusConfig.label} banner`"
|
:aria-label="`Dismiss ${statusConfig.label} banner`"
|
||||||
>
|
>
|
||||||
<Icon name="heroicons:x-mark" class="w-5 h-5" />
|
<Icon name="heroicons:x-mark" class="w-5 h-5" />
|
||||||
|
|
@ -119,7 +119,7 @@ const getActionButtonClass = (color) => {
|
||||||
const colorClasses = {
|
const colorClasses = {
|
||||||
orange: "bg-orange-600 text-white hover:bg-orange-700",
|
orange: "bg-orange-600 text-white hover:bg-orange-700",
|
||||||
blue: "bg-blue-600 text-white hover:bg-blue-700",
|
blue: "bg-blue-600 text-white hover:bg-blue-700",
|
||||||
gray: "bg-ghost-700 text-ghost-100 hover:bg-ghost-600",
|
gray: "bg-guild-700 text-guild-100 hover:bg-guild-600",
|
||||||
};
|
};
|
||||||
return `${baseClass} ${colorClasses[color] || colorClasses.blue}`;
|
return `${baseClass} ${colorClasses[color] || colorClasses.blue}`;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@
|
||||||
v-if="showInteractiveArea"
|
v-if="showInteractiveArea"
|
||||||
:class="[
|
:class="[
|
||||||
'rounded-2xl p-6 md:p-8 mb-12 backdrop-blur-sm',
|
'rounded-2xl p-6 md:p-8 mb-12 backdrop-blur-sm',
|
||||||
props.theme === 'ethereal'
|
props.theme === 'guild'
|
||||||
? 'bg-ghost-800/60 border border-ghost-700 ethereal-glow halftone-texture'
|
? 'bg-guild-800/60 border border-guild-700 candlelight-glow halftone-texture'
|
||||||
: 'bg-[--ui-bg-elevated] shadow-xl border border-blue-200',
|
: 'bg-[--ui-bg-elevated] shadow-xl border border-blue-200',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
|
|
@ -35,8 +35,8 @@
|
||||||
<button
|
<button
|
||||||
:class="[
|
:class="[
|
||||||
'p-2 md:p-3 rounded-full transition-all duration-300 flex-shrink-0',
|
'p-2 md:p-3 rounded-full transition-all duration-300 flex-shrink-0',
|
||||||
props.theme === 'ethereal'
|
props.theme === 'guild'
|
||||||
? 'bg-whisper-600/80 text-ghost-100 hover:bg-whisper-500 ethereal-glow'
|
? 'bg-candlelight-600/80 text-guild-100 hover:bg-candlelight-500 candlelight-glow'
|
||||||
: 'bg-blue-500 text-white hover:bg-blue-600',
|
: 'bg-blue-500 text-white hover:bg-blue-600',
|
||||||
]"
|
]"
|
||||||
@click="$emit('prev')"
|
@click="$emit('prev')"
|
||||||
|
|
@ -61,8 +61,8 @@
|
||||||
<p
|
<p
|
||||||
:class="[
|
:class="[
|
||||||
'text-base md:text-lg',
|
'text-base md:text-lg',
|
||||||
props.theme === 'ethereal'
|
props.theme === 'guild'
|
||||||
? 'text-ghost-200'
|
? 'text-guild-200'
|
||||||
: 'text-[--ui-text-muted]',
|
: 'text-[--ui-text-muted]',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
|
|
@ -77,8 +77,8 @@
|
||||||
<button
|
<button
|
||||||
:class="[
|
:class="[
|
||||||
'p-2 md:p-3 rounded-full transition-all duration-300 flex-shrink-0',
|
'p-2 md:p-3 rounded-full transition-all duration-300 flex-shrink-0',
|
||||||
props.theme === 'ethereal'
|
props.theme === 'guild'
|
||||||
? 'bg-whisper-600/80 text-ghost-100 hover:bg-whisper-500 ethereal-glow'
|
? 'bg-candlelight-600/80 text-guild-100 hover:bg-candlelight-500 candlelight-glow'
|
||||||
: 'bg-blue-500 text-white hover:bg-blue-600',
|
: 'bg-blue-500 text-white hover:bg-blue-600',
|
||||||
]"
|
]"
|
||||||
@click="$emit('next')"
|
@click="$emit('next')"
|
||||||
|
|
@ -137,7 +137,7 @@ const props = defineProps({
|
||||||
type: String,
|
type: String,
|
||||||
default: "blue",
|
default: "blue",
|
||||||
validator: (value) =>
|
validator: (value) =>
|
||||||
["blue", "purple", "emerald", "gray", "ethereal"].includes(value),
|
["blue", "purple", "emerald", "gray", "guild"].includes(value),
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
@ -182,8 +182,8 @@ const backgroundClass = computed(() => {
|
||||||
purple: "bg-gradient-to-br from-purple-50 to-violet-100",
|
purple: "bg-gradient-to-br from-purple-50 to-violet-100",
|
||||||
emerald: "bg-gradient-to-br from-emerald-50 to-teal-100",
|
emerald: "bg-gradient-to-br from-emerald-50 to-teal-100",
|
||||||
gray: "bg-neutral-100",
|
gray: "bg-neutral-100",
|
||||||
ethereal:
|
guild:
|
||||||
"bg-gradient-to-br from-ghost-900 via-ghost-800 to-whisper-900 halftone-texture",
|
"bg-gradient-to-br from-guild-900 via-guild-800 to-candlelight-900 halftone-texture",
|
||||||
};
|
};
|
||||||
return themes[props.theme] || themes.blue;
|
return themes[props.theme] || themes.blue;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex items-center gap-3 text-sm">
|
<div class="flex items-center gap-3 text-sm">
|
||||||
<span class="text-gray-700 dark:text-ghost-400 text-xs font-medium"
|
<span class="text-gray-700 dark:text-guild-400 text-xs font-medium"
|
||||||
>{{ label }}:</span
|
>{{ label }}:</span
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
class="text-xs transition-colors"
|
class="text-xs transition-colors"
|
||||||
:class="
|
:class="
|
||||||
isPrivate
|
isPrivate
|
||||||
? 'text-gray-500 dark:text-ghost-500'
|
? 'text-gray-500 dark:text-guild-500'
|
||||||
: 'text-blue-600 dark:text-blue-400 font-semibold'
|
: 'text-blue-600 dark:text-blue-400 font-semibold'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
:class="
|
:class="
|
||||||
isPrivate
|
isPrivate
|
||||||
? 'text-blue-600 dark:text-blue-400 font-semibold'
|
? 'text-blue-600 dark:text-blue-400 font-semibold'
|
||||||
: 'text-gray-500 dark:text-ghost-500'
|
: 'text-gray-500 dark:text-guild-500'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
Private
|
Private
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
<!-- Registration Form -->
|
<!-- Registration Form -->
|
||||||
<div
|
<div
|
||||||
v-if="passInfo.available && !passInfo.alreadyRegistered"
|
v-if="passInfo.available && !passInfo.alreadyRegistered"
|
||||||
class="bg-ghost-800/50 dark:bg-ghost-700/30 rounded-xl border border-ghost-600 dark:border-ghost-600 p-6"
|
class="bg-guild-800/50 dark:bg-guild-700/30 rounded-xl border border-guild-600 dark:border-guild-600 p-6"
|
||||||
>
|
>
|
||||||
<h3 class="text-xl font-bold text-[--ui-text] mb-6">
|
<h3 class="text-xl font-bold text-[--ui-text] mb-6">
|
||||||
{{
|
{{
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="w-12 h-12 rounded-full bg-ghost-700 flex items-center justify-center text-ghost-300 font-bold"
|
class="w-12 h-12 rounded-full bg-guild-700 flex items-center justify-center text-guild-300 font-bold"
|
||||||
>
|
>
|
||||||
{{ update.author?.name?.charAt(0)?.toUpperCase() || "?" }}
|
{{ update.author?.name?.charAt(0)?.toUpperCase() || "?" }}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -23,30 +23,30 @@
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex items-start justify-between gap-4 mb-2">
|
<div class="flex items-start justify-between gap-4 mb-2">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-semibold text-ghost-100">
|
<h3 class="font-semibold text-guild-100">
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
v-if="update.author?._id"
|
v-if="update.author?._id"
|
||||||
:to="`/updates/user/${update.author._id}`"
|
:to="`/updates/user/${update.author._id}`"
|
||||||
class="hover:text-ghost-300 transition-colors"
|
class="hover:text-guild-300 transition-colors"
|
||||||
>
|
>
|
||||||
{{ update.author.name }}
|
{{ update.author.name }}
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<span v-else>Unknown Member</span>
|
<span v-else>Unknown Member</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div class="flex items-center gap-2 text-sm text-ghost-400">
|
<div class="flex items-center gap-2 text-sm text-guild-400">
|
||||||
<time :datetime="update.createdAt">
|
<time :datetime="update.createdAt">
|
||||||
{{ formatDate(update.createdAt) }}
|
{{ formatDate(update.createdAt) }}
|
||||||
</time>
|
</time>
|
||||||
<span v-if="isEdited" class="text-ghost-500">(edited)</span>
|
<span v-if="isEdited" class="text-guild-500">(edited)</span>
|
||||||
<span
|
<span
|
||||||
v-if="update.privacy === 'private'"
|
v-if="update.privacy === 'private'"
|
||||||
class="px-2 py-0.5 bg-ghost-700 text-ghost-300 rounded text-xs"
|
class="px-2 py-0.5 bg-guild-700 text-guild-300 rounded text-xs"
|
||||||
>
|
>
|
||||||
Private
|
Private
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="update.privacy === 'public'"
|
v-if="update.privacy === 'public'"
|
||||||
class="px-2 py-0.5 bg-ghost-700 text-ghost-300 rounded text-xs"
|
class="px-2 py-0.5 bg-guild-700 text-guild-300 rounded text-xs"
|
||||||
>
|
>
|
||||||
Public
|
Public
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -73,12 +73,12 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<div class="text-ghost-200 whitespace-pre-wrap break-words mb-3">
|
<div class="text-guild-200 whitespace-pre-wrap break-words mb-3">
|
||||||
<template v-if="showPreview && update.content.length > 300">
|
<template v-if="showPreview && update.content.length > 300">
|
||||||
{{ update.content.substring(0, 300) }}...
|
{{ update.content.substring(0, 300) }}...
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
:to="`/updates/${update._id}`"
|
:to="`/updates/${update._id}`"
|
||||||
class="text-ghost-400 hover:text-ghost-300 ml-1"
|
class="text-guild-400 hover:text-guild-300 ml-1"
|
||||||
>
|
>
|
||||||
Read more
|
Read more
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
@ -100,14 +100,14 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Footer actions -->
|
<!-- Footer actions -->
|
||||||
<div class="flex items-center gap-4 text-sm text-ghost-400">
|
<div class="flex items-center gap-4 text-sm text-guild-400">
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
:to="`/updates/${update._id}`"
|
:to="`/updates/${update._id}`"
|
||||||
class="hover:text-ghost-300 transition-colors"
|
class="hover:text-guild-300 transition-colors"
|
||||||
>
|
>
|
||||||
View full update
|
View full update
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<span v-if="update.commentsEnabled" class="text-ghost-500">
|
<span v-if="update.commentsEnabled" class="text-guild-500">
|
||||||
Comments (coming soon)
|
Comments (coming soon)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -175,15 +175,15 @@ const formatDate = (date) => {
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.update-card {
|
.update-card {
|
||||||
background-color: rgb(41 37 36);
|
background-color: var(--color-guild-800);
|
||||||
border-color: rgb(87 83 78);
|
border-color: var(--color-guild-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
.update-card:hover {
|
.update-card:hover {
|
||||||
border-color: rgb(120 113 108);
|
border-color: var(--color-guild-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.card) {
|
:deep(.card) {
|
||||||
background-color: rgb(41 37 36);
|
background-color: var(--color-guild-800);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -11,19 +11,19 @@
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
|
||||||
<!-- Privacy Settings -->
|
<!-- Privacy Settings -->
|
||||||
<div class="border border-ghost-700 rounded-lg p-4 bg-ghost-800/30">
|
<div class="border border-guild-700 rounded-lg p-4 bg-guild-800/30">
|
||||||
<h3 class="text-sm font-medium text-ghost-200 mb-4">Privacy Settings</h3>
|
<h3 class="text-sm font-medium text-guild-200 mb-4">Privacy Settings</h3>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<label class="flex items-center gap-3 cursor-pointer">
|
<label class="flex items-center gap-3 cursor-pointer">
|
||||||
<input
|
<input
|
||||||
v-model="formData.privacy"
|
v-model="formData.privacy"
|
||||||
type="radio"
|
type="radio"
|
||||||
value="public"
|
value="public"
|
||||||
class="w-4 h-4 text-ghost-400"
|
class="w-4 h-4 text-guild-400"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-ghost-200 font-medium">Public</div>
|
<div class="text-guild-200 font-medium">Public</div>
|
||||||
<div class="text-sm text-ghost-400">
|
<div class="text-sm text-guild-400">
|
||||||
Visible to everyone, including non-members
|
Visible to everyone, including non-members
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -34,11 +34,11 @@
|
||||||
v-model="formData.privacy"
|
v-model="formData.privacy"
|
||||||
type="radio"
|
type="radio"
|
||||||
value="members"
|
value="members"
|
||||||
class="w-4 h-4 text-ghost-400"
|
class="w-4 h-4 text-guild-400"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-ghost-200 font-medium">Members Only</div>
|
<div class="text-guild-200 font-medium">Members Only</div>
|
||||||
<div class="text-sm text-ghost-400">
|
<div class="text-sm text-guild-400">
|
||||||
Only visible to Ghost Guild members
|
Only visible to Ghost Guild members
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -49,11 +49,11 @@
|
||||||
v-model="formData.privacy"
|
v-model="formData.privacy"
|
||||||
type="radio"
|
type="radio"
|
||||||
value="private"
|
value="private"
|
||||||
class="w-4 h-4 text-ghost-400"
|
class="w-4 h-4 text-guild-400"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-ghost-200 font-medium">Private</div>
|
<div class="text-guild-200 font-medium">Private</div>
|
||||||
<div class="text-sm text-ghost-400">Only visible to you</div>
|
<div class="text-sm text-guild-400">Only visible to you</div>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -66,8 +66,8 @@
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<USwitch v-model="formData.commentsEnabled" />
|
<USwitch v-model="formData.commentsEnabled" />
|
||||||
<div>
|
<div>
|
||||||
<div class="text-ghost-200 font-medium">Enable Comments</div>
|
<div class="text-guild-200 font-medium">Enable Comments</div>
|
||||||
<div class="text-sm text-ghost-400">
|
<div class="text-sm text-guild-400">
|
||||||
Allow members to comment on this update
|
Allow members to comment on this update
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -75,7 +75,7 @@
|
||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<div
|
<div
|
||||||
class="flex justify-between items-center pt-4 border-t border-ghost-700"
|
class="flex justify-between items-center pt-4 border-t border-guild-700"
|
||||||
>
|
>
|
||||||
<UButton variant="ghost" color="neutral" @click="$emit('cancel')">
|
<UButton variant="ghost" color="neutral" @click="$emit('cancel')">
|
||||||
Cancel
|
Cancel
|
||||||
|
|
@ -157,28 +157,28 @@ watch(
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* Field labels */
|
/* Field labels */
|
||||||
:deep(label) {
|
:deep(label) {
|
||||||
color: rgb(231 229 228) !important;
|
color: var(--color-guild-200) !important;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Textarea styling */
|
/* Textarea styling */
|
||||||
:deep(textarea) {
|
:deep(textarea) {
|
||||||
background-color: rgb(41 37 36) !important;
|
background-color: var(--color-guild-800) !important;
|
||||||
color: rgb(231 229 228) !important;
|
color: var(--color-guild-200) !important;
|
||||||
border-color: rgb(87 83 78) !important;
|
border-color: var(--color-guild-600) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(textarea::placeholder) {
|
:deep(textarea::placeholder) {
|
||||||
color: rgb(120 113 108) !important;
|
color: var(--color-guild-500) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(textarea:focus) {
|
:deep(textarea:focus) {
|
||||||
border-color: rgb(168 162 158) !important;
|
border-color: var(--color-guild-400) !important;
|
||||||
background-color: rgb(44 40 39) !important;
|
background-color: var(--color-guild-700) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Radio buttons */
|
/* Radio buttons */
|
||||||
input[type="radio"] {
|
input[type="radio"] {
|
||||||
accent-color: rgb(168 162 158);
|
accent-color: var(--color-candlelight-600);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,48 @@
|
||||||
// Central configuration for Ghost Guild Circles
|
// Central configuration for Ghost Guild Circles
|
||||||
export const CIRCLES = {
|
export const CIRCLES = {
|
||||||
COMMUNITY: {
|
COMMUNITY: {
|
||||||
value: 'community',
|
value: "community",
|
||||||
label: 'Community Circle',
|
label: "Community",
|
||||||
description: 'For individuals interested in learning about cooperative principles',
|
shortDescription: "Learning about co-ops",
|
||||||
|
description:
|
||||||
|
"For individuals interested in learning about cooperative principles",
|
||||||
features: [
|
features: [
|
||||||
'Game workers curious about co-ops, researchers, industry allies',
|
"Game workers curious about coops",
|
||||||
'People exploring alternative work models',
|
"People exploring alternative work models, including researchers and industry allies",
|
||||||
'Access to community forums and resources'
|
"Access to community forums and resources",
|
||||||
]
|
],
|
||||||
|
colorToken: "circle-community",
|
||||||
|
metaphor: "Reading Room",
|
||||||
|
icon: null,
|
||||||
},
|
},
|
||||||
FOUNDER: {
|
FOUNDER: {
|
||||||
value: 'founder',
|
value: "founder",
|
||||||
label: 'Founder Circle',
|
label: "Founders",
|
||||||
description: 'For those actively establishing or growing their cooperative studio',
|
shortDescription: "Building your studio",
|
||||||
|
description: "For those actively establishing or growing their coop",
|
||||||
features: [
|
features: [
|
||||||
'Has two tracks: Peer Accelerator Prep Track and Indie Track',
|
"Teams working toward applying for the Peer Accelerator",
|
||||||
'Teams working toward PA application',
|
"Early-stage coop studios",
|
||||||
'Early-stage co-op studios',
|
"Studios transitioning to coop model",
|
||||||
'Studios transitioning to co-op model'
|
],
|
||||||
]
|
colorToken: "circle-founder",
|
||||||
|
metaphor: "Workshop",
|
||||||
|
icon: null,
|
||||||
},
|
},
|
||||||
PRACTITIONER: {
|
PRACTITIONER: {
|
||||||
value: 'practitioner',
|
value: "practitioner",
|
||||||
label: 'Practitioner Circle',
|
label: "Practitioners",
|
||||||
description: 'For Peer Accelerator alumni and experienced studio leaders',
|
shortDescription: "Leading and mentoring",
|
||||||
|
description: "For Peer Accelerator alumni and experienced studio founders",
|
||||||
features: [
|
features: [
|
||||||
'Those implementing cooperative models',
|
"Those implementing cooperative models",
|
||||||
'Industry mentors and advisors',
|
"Industry mentors and advisors",
|
||||||
'Expert-level workshops and mentorship opportunities'
|
"Expert-level workshops and mentorship opportunities",
|
||||||
]
|
],
|
||||||
}
|
colorToken: "circle-practitioner",
|
||||||
|
metaphor: "Alcove",
|
||||||
|
icon: null,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get all circle options as an array (useful for forms)
|
// Get all circle options as an array (useful for forms)
|
||||||
|
|
@ -40,15 +52,15 @@ export const getCircleOptions = () => {
|
||||||
|
|
||||||
// Get valid circle values for validation
|
// Get valid circle values for validation
|
||||||
export const getValidCircleValues = () => {
|
export const getValidCircleValues = () => {
|
||||||
return Object.values(CIRCLES).map(circle => circle.value);
|
return Object.values(CIRCLES).map((circle) => circle.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get circle by value
|
// Get circle by value
|
||||||
export const getCircleByValue = (value) => {
|
export const getCircleByValue = (value) => {
|
||||||
return Object.values(CIRCLES).find(circle => circle.value === value);
|
return Object.values(CIRCLES).find((circle) => circle.value === value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if a circle value is valid
|
// Check if a circle value is valid
|
||||||
export const isValidCircleValue = (value) => {
|
export const isValidCircleValue = (value) => {
|
||||||
return getValidCircleValues().includes(value);
|
return getValidCircleValues().includes(value);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="min-h-screen bg-ghost-900">
|
<div class="min-h-screen bg-guild-900">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="min-h-screen bg-ghost-900 relative">
|
<div class="min-h-screen bg-guild-900 relative">
|
||||||
<!-- Background image at top - full page width -->
|
<!-- Background image at top - full page width -->
|
||||||
<div
|
<div
|
||||||
class="absolute inset-x-0 pointer-events-none z-0"
|
class="absolute inset-x-0 pointer-events-none z-0"
|
||||||
|
|
@ -23,12 +23,12 @@
|
||||||
|
|
||||||
<!-- Mobile Header -->
|
<!-- Mobile Header -->
|
||||||
<div
|
<div
|
||||||
class="lg:hidden fixed top-0 left-0 right-0 z-50 bg-ghost-900/95 backdrop-blur-md border-b border-ghost-700"
|
class="lg:hidden fixed top-0 left-0 right-0 z-50 bg-guild-900/95 backdrop-blur-md border-b border-guild-700"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between p-4">
|
<div class="flex items-center justify-between p-4">
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
to="/"
|
to="/"
|
||||||
class="text-lg font-bold text-white ethereal-text tracking-wider"
|
class="text-lg font-bold text-white warm-text tracking-wider"
|
||||||
>
|
>
|
||||||
Ghost Guild
|
Ghost Guild
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
@ -46,7 +46,7 @@
|
||||||
<!-- Container to center content and sidebar together -->
|
<!-- Container to center content and sidebar together -->
|
||||||
<div class="lg:flex lg:justify-center lg:gap-0">
|
<div class="lg:flex lg:justify-center lg:gap-0">
|
||||||
<!-- Main Content Column -->
|
<!-- Main Content Column -->
|
||||||
<div class="lg:w-[800px] overflow-y-auto relative z-[5]">
|
<div class="lg:w-[800px] overflow-y-auto relative z-[5] guild-interior">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
131
app/layouts/landing.vue
Normal file
131
app/layouts/landing.vue
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
<template>
|
||||||
|
<div class="min-h-screen bg-guild-900">
|
||||||
|
<!-- Horizontal Navigation -->
|
||||||
|
<nav class="w-full px-6 md:px-8 py-4">
|
||||||
|
<div class="max-w-6xl mx-auto flex items-center justify-between">
|
||||||
|
<!-- Logo/Wordmark -->
|
||||||
|
<NuxtLink to="/" class="text-xl font-bold text-primary-400 tracking-wide">
|
||||||
|
Ghost Guild
|
||||||
|
</NuxtLink>
|
||||||
|
|
||||||
|
<!-- Desktop Navigation Links -->
|
||||||
|
<div class="hidden md:flex items-center gap-8">
|
||||||
|
<NuxtLink
|
||||||
|
to="/about"
|
||||||
|
class="text-guild-300 hover:text-guild-100 transition-colors text-sm"
|
||||||
|
>
|
||||||
|
About
|
||||||
|
</NuxtLink>
|
||||||
|
<NuxtLink
|
||||||
|
to="/events"
|
||||||
|
class="text-guild-300 hover:text-guild-100 transition-colors text-sm"
|
||||||
|
>
|
||||||
|
Events
|
||||||
|
</NuxtLink>
|
||||||
|
<template v-if="isAuthenticated">
|
||||||
|
<NuxtLink
|
||||||
|
to="/member/dashboard"
|
||||||
|
class="text-primary-400 hover:text-primary-300 transition-colors text-sm font-medium"
|
||||||
|
>
|
||||||
|
Dashboard
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<button
|
||||||
|
@click="openLoginModal"
|
||||||
|
class="text-primary-400 hover:text-primary-300 transition-colors text-sm font-medium"
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile Menu Button -->
|
||||||
|
<UButton
|
||||||
|
icon="i-lucide-menu"
|
||||||
|
color="neutral"
|
||||||
|
variant="ghost"
|
||||||
|
size="md"
|
||||||
|
class="md:hidden"
|
||||||
|
@click="isMobileMenuOpen = true"
|
||||||
|
aria-label="Open menu"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main>
|
||||||
|
<slot />
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="border-t border-guild-800 py-8 px-6 md:px-8">
|
||||||
|
<div class="max-w-6xl mx-auto flex flex-col md:flex-row justify-between items-center gap-4">
|
||||||
|
<p class="text-guild-500 text-sm">
|
||||||
|
© {{ currentYear }} Ghost Guild
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="mailto:hello@ghostguild.org"
|
||||||
|
class="text-guild-500 hover:text-guild-300 transition-colors text-sm"
|
||||||
|
>
|
||||||
|
hello@ghostguild.org
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<!-- Mobile Navigation Drawer -->
|
||||||
|
<USlideover v-model:open="isMobileMenuOpen" side="right">
|
||||||
|
<template #body>
|
||||||
|
<div class="p-6 space-y-6">
|
||||||
|
<NuxtLink
|
||||||
|
to="/about"
|
||||||
|
class="block text-guild-200 hover:text-guild-100 transition-colors text-lg"
|
||||||
|
@click="isMobileMenuOpen = false"
|
||||||
|
>
|
||||||
|
About
|
||||||
|
</NuxtLink>
|
||||||
|
<NuxtLink
|
||||||
|
to="/events"
|
||||||
|
class="block text-guild-200 hover:text-guild-100 transition-colors text-lg"
|
||||||
|
@click="isMobileMenuOpen = false"
|
||||||
|
>
|
||||||
|
Events
|
||||||
|
</NuxtLink>
|
||||||
|
<template v-if="isAuthenticated">
|
||||||
|
<NuxtLink
|
||||||
|
to="/member/dashboard"
|
||||||
|
class="block text-primary-400 hover:text-primary-300 transition-colors text-lg font-medium"
|
||||||
|
@click="isMobileMenuOpen = false"
|
||||||
|
>
|
||||||
|
Dashboard
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<button
|
||||||
|
@click="handleMobileSignIn"
|
||||||
|
class="block text-primary-400 hover:text-primary-300 transition-colors text-lg font-medium"
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</USlideover>
|
||||||
|
|
||||||
|
<!-- Login Modal -->
|
||||||
|
<LoginModal />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const { isAuthenticated } = useAuth()
|
||||||
|
const { openLoginModal } = useLoginModal()
|
||||||
|
|
||||||
|
const isMobileMenuOpen = ref(false)
|
||||||
|
const currentYear = new Date().getFullYear()
|
||||||
|
|
||||||
|
const handleMobileSignIn = () => {
|
||||||
|
isMobileMenuOpen.value = false
|
||||||
|
openLoginModal()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -11,37 +11,116 @@
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<section class="py-20 bg-[--ui-bg]">
|
<section class="py-20 bg-[--ui-bg]">
|
||||||
<UContainer>
|
<UContainer>
|
||||||
<div class="max-w-3xl">
|
<div class="max-w-3xl prose prose-lg dark:prose-invert">
|
||||||
<!-- TODO: Add copy about history and connection to Baby Ghosts -->
|
<!-- directives:[] -->
|
||||||
<p class="text-lg text-[--ui-text-muted]">
|
<div id="content">
|
||||||
Ghost Guild is a community of learning and practice for anyone,
|
<p>
|
||||||
anywhere interested in a video game industry-wide shift to
|
Ghost Guild is a community of learning and practice for anyone,
|
||||||
worker-owned studio models. It is also the membership program of
|
anywhere interested in a video game industry-wide shift to
|
||||||
Baby Ghosts, a Canadian nonprofit that provides resources and
|
worker-owned studio models.
|
||||||
support for worker-owned studios. After running our Peer Accelerator
|
</p>
|
||||||
program for three years, we are now scaling up our operations to
|
<p>
|
||||||
support more studios and expand our reach. As we build our knowledge
|
We reject hierarchy wherever it shows up. Ghost Guild is
|
||||||
commons, more folks unable to participate in the program can benefit
|
tier-less, but peer-full.
|
||||||
from collectively compiled knowledge and find community.
|
</p>
|
||||||
</p>
|
<h2 id="our-story">Our Story</h2>
|
||||||
<p>
|
<p>
|
||||||
something here about the work to make Slack integration smooth and
|
Ghost Guild is the membership program of Baby Ghosts, a Canadian
|
||||||
safe; more about purpose??
|
nonprofit that provides resources and support for worker-owned
|
||||||
</p>
|
game studios.
|
||||||
<p>
|
</p>
|
||||||
We are pretty interested in saying _fuck you_ to hierarchy however
|
<p>
|
||||||
it shows up in our work. So the Ghost Guild membership program is
|
For three years, Baby Ghosts has run the Peer Accelerator - an
|
||||||
tier-less… but peer-full. We've loosely named some circles you can
|
intensive program pairing early-stage studios with mentorship,
|
||||||
join that will help us connect you with folks at the same stage of
|
peer learning, and grants. Twenty-five studios have graduated. The
|
||||||
development as you, and with resources that are in line with your
|
model works! Peer learning builds stronger foundations than
|
||||||
needs and interests. But none of these circles is superior, and
|
top-down advice ever could.
|
||||||
there's no harm or shame in sticking with one for a while, or moving
|
</p>
|
||||||
between them to find the best fit. Choosing your financial
|
<p>
|
||||||
contribution level is also not about paying for access to additional
|
But not everyone can commit to a six-month cohort. Some folks are
|
||||||
resources - everything is available to every member, no matter their
|
still exploring. Others are already running established
|
||||||
circle or contribution level. Rather, it's about finding a dues
|
cooperatives and want to give back. Many are scattered across the
|
||||||
level that's meaningful to you but not a burden.
|
world and just need to know they're not alone in wanting to build
|
||||||
</p>
|
something different.
|
||||||
|
</p>
|
||||||
|
<p><em>Ghost Guild is how we open the doors wider.</em></p>
|
||||||
|
<p>
|
||||||
|
As we build our knowledge commons, more folks can benefit from
|
||||||
|
collectively compiled wisdom and find community - whether or not
|
||||||
|
they ever apply to the Peer Accelerator. The intensive program
|
||||||
|
continues. Ghost Guild expands access to everything around it.
|
||||||
|
</p>
|
||||||
|
<h2 id="the-circles">The Circles</h2>
|
||||||
|
<p>
|
||||||
|
We've loosely named some circles you can join. This is not to rank
|
||||||
|
you, but to connect you with folks at a similar stage and with
|
||||||
|
resources that fit where you are right now.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
No circle is superior. There's no shame in sticking with one for a
|
||||||
|
while, or moving between them to find the best fit.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The Community Circle is for individuals exploring cooperative
|
||||||
|
principles. Whether you're working in the industry or in academia,
|
||||||
|
you'll get access to the knowledge commons, workshops, resources,
|
||||||
|
guides, community Slack and peer support, and social events and
|
||||||
|
networking.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The Founder Circle is for those actively building a worker-owned
|
||||||
|
studio. You'll have access to everything within the platform, just
|
||||||
|
like any other member, but you might be particularly interested in
|
||||||
|
peer matching with studios at similar stages and Peer Accelerator
|
||||||
|
alumni, and templates for governance, financial modelling, and
|
||||||
|
decision-making.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The Practitioner Circle is for Peer Accelerator alumni and
|
||||||
|
experienced cooperative studio leaders. You'll hopefully find
|
||||||
|
yourself providing paid support to other members, as well as
|
||||||
|
engaging in collaborative research opportunities with academic
|
||||||
|
partners, connecting to other coops for business development, and
|
||||||
|
helping build a platform for changing industry practices.
|
||||||
|
</p>
|
||||||
|
<h2 id="how-contribution-works">How Contribution Works</h2>
|
||||||
|
<p>
|
||||||
|
Choosing your financial contribution is also not about paying for
|
||||||
|
access. Everything is available to every member, no matter their
|
||||||
|
circle or contribution level.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Rather, it's about finding a dues level that's meaningful to you
|
||||||
|
without being a burden.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The knowledge commons is open to all Ghosties. Your contribution
|
||||||
|
sustains a community you believe in.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If dues are a barrier, that's okay. Members who are able to
|
||||||
|
contribute more can direct additional funds to the Solidarity
|
||||||
|
Fund, which covers dues for those who need support.
|
||||||
|
</p>
|
||||||
|
<h2 id="community">Community</h2>
|
||||||
|
<p>
|
||||||
|
When you join Ghost Guild, you join a community of Ghosties -
|
||||||
|
folks at every stage of the journey, learning from each other.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Our Slack community is built with care. New members are welcomed
|
||||||
|
thoughtfully, channels are structured to help you find your
|
||||||
|
people, and we grow at a pace that protects what makes this space
|
||||||
|
special.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This is a cascading mentorship structure where everyone is both
|
||||||
|
learning and teaching. Practitioners mentor Founders. Founders
|
||||||
|
mentor Community members. And Community members bring fresh
|
||||||
|
perspectives that keep everyone honest.
|
||||||
|
</p>
|
||||||
|
<p>Welcome, Ghostie! 👻</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</UContainer>
|
</UContainer>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,23 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="pending"
|
v-if="pending"
|
||||||
class="min-h-screen bg-ghost-900 flex items-center justify-center"
|
class="min-h-screen bg-guild-900 flex items-center justify-center"
|
||||||
>
|
>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div
|
<div
|
||||||
class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto mb-4"
|
class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto mb-4"
|
||||||
></div>
|
></div>
|
||||||
<p class="text-ghost-200">Loading event details...</p>
|
<p class="text-guild-200">Loading event details...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-else-if="error"
|
v-else-if="error"
|
||||||
class="min-h-screen bg-ghost-900 flex items-center justify-center"
|
class="min-h-screen bg-guild-900 flex items-center justify-center"
|
||||||
>
|
>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h2 class="text-2xl font-bold text-ghost-100 mb-2">Event Not Found</h2>
|
<h2 class="text-2xl font-bold text-guild-100 mb-2">Event Not Found</h2>
|
||||||
<p class="text-ghost-300 mb-6">
|
<p class="text-guild-300 mb-6">
|
||||||
The event you're looking for doesn't exist.
|
The event you're looking for doesn't exist.
|
||||||
</p>
|
</p>
|
||||||
<NuxtLink to="/events" class="text-blue-400 hover:underline">
|
<NuxtLink to="/events" class="text-blue-400 hover:underline">
|
||||||
|
|
@ -74,35 +74,35 @@
|
||||||
<PageHeader v-else :title="event.title" theme="blue" size="medium" />
|
<PageHeader v-else :title="event.title" theme="blue" size="medium" />
|
||||||
|
|
||||||
<!-- Event Details Section -->
|
<!-- Event Details Section -->
|
||||||
<section class="py-16 bg-ghost-900">
|
<section class="py-16 bg-guild-900">
|
||||||
<UContainer>
|
<UContainer>
|
||||||
<div class="max-w-4xl mx-auto">
|
<div class="max-w-4xl mx-auto">
|
||||||
<!-- Event Meta Info -->
|
<!-- Event Meta Info -->
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm text-ghost-400">Date</p>
|
<p class="text-sm text-guild-400">Date</p>
|
||||||
<p class="font-semibold text-ghost-100">
|
<p class="font-semibold text-guild-100">
|
||||||
{{ formatDate(event.startDate) }}
|
{{ formatDate(event.startDate) }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm text-ghost-400">Time</p>
|
<p class="text-sm text-guild-400">Time</p>
|
||||||
<p class="font-semibold text-ghost-100">
|
<p class="font-semibold text-guild-100">
|
||||||
{{ formatTime(event.startDate, event.endDate) }}
|
{{ formatTime(event.startDate, event.endDate) }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm text-ghost-400">Location</p>
|
<p class="text-sm text-guild-400">Location</p>
|
||||||
<p class="font-semibold text-ghost-100">
|
<p class="font-semibold text-guild-100">
|
||||||
{{ event.location }}
|
{{ event.location }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm text-ghost-400">Calendar</p>
|
<p class="text-sm text-guild-400">Calendar</p>
|
||||||
<UButton
|
<UButton
|
||||||
:href="`/api/events/${route.params.id}/calendar`"
|
:href="`/api/events/${route.params.id}/calendar`"
|
||||||
download
|
download
|
||||||
|
|
@ -150,7 +150,7 @@
|
||||||
>
|
>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<span
|
<span
|
||||||
class="text-sm font-medium text-gray-800 dark:text-ghost-200"
|
class="text-sm font-medium text-gray-800 dark:text-guild-200"
|
||||||
>Recommended for:</span
|
>Recommended for:</span
|
||||||
>
|
>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
|
|
@ -167,31 +167,31 @@
|
||||||
|
|
||||||
<!-- Event Description -->
|
<!-- Event Description -->
|
||||||
<div class="prose prose-lg dark:prose-invert max-w-none mb-12">
|
<div class="prose prose-lg dark:prose-invert max-w-none mb-12">
|
||||||
<h2 class="text-2xl font-bold text-ghost-100 mb-4">
|
<h2 class="text-2xl font-bold text-guild-100 mb-4">
|
||||||
About This Event
|
About This Event
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<!-- Series Description -->
|
<!-- Series Description -->
|
||||||
<div
|
<div
|
||||||
v-if="event.series?.isSeriesEvent && event.series.description"
|
v-if="event.series?.isSeriesEvent && event.series.description"
|
||||||
class="event-series-description mb-6 p-4 bg-ghost-800/30 dark:bg-ghost-700/20 rounded-lg border border-ghost-600 dark:border-ghost-600"
|
class="event-series-description mb-6 p-4 bg-guild-800/30 dark:bg-guild-700/20 rounded-lg border border-guild-600 dark:border-guild-600"
|
||||||
>
|
>
|
||||||
<h3
|
<h3
|
||||||
class="event-series-description__title text-lg font-semibold text-ghost-100 dark:text-ghost-100 mb-2"
|
class="event-series-description__title text-lg font-semibold text-guild-100 dark:text-guild-100 mb-2"
|
||||||
>
|
>
|
||||||
About the {{ event.series.title }} Series
|
About the {{ event.series.title }} Series
|
||||||
</h3>
|
</h3>
|
||||||
<p class="event-series-description__text text-ghost-200">
|
<p class="event-series-description__text text-guild-200">
|
||||||
{{ event.series.description }}
|
{{ event.series.description }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-ghost-200">
|
<p class="text-guild-200">
|
||||||
{{ event.description }}
|
{{ event.description }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div v-if="event.agenda && event.agenda.length > 0" class="mt-8">
|
<div v-if="event.agenda && event.agenda.length > 0" class="mt-8">
|
||||||
<h3 class="text-xl font-semibold text-ghost-100 mb-4">
|
<h3 class="text-xl font-semibold text-guild-100 mb-4">
|
||||||
Event Agenda
|
Event Agenda
|
||||||
</h3>
|
</h3>
|
||||||
<ul class="space-y-3">
|
<ul class="space-y-3">
|
||||||
|
|
@ -205,7 +205,7 @@
|
||||||
>
|
>
|
||||||
{{ index + 1 }}
|
{{ index + 1 }}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-ghost-200">{{ item }}</span>
|
<span class="text-guild-200">{{ item }}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -214,7 +214,7 @@
|
||||||
v-if="event.speakers && event.speakers.length > 0"
|
v-if="event.speakers && event.speakers.length > 0"
|
||||||
class="mt-8"
|
class="mt-8"
|
||||||
>
|
>
|
||||||
<h3 class="text-xl font-semibold text-ghost-100 mb-4">
|
<h3 class="text-xl font-semibold text-guild-100 mb-4">
|
||||||
Speakers
|
Speakers
|
||||||
</h3>
|
</h3>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
|
@ -224,13 +224,13 @@
|
||||||
class="flex items-start space-x-4"
|
class="flex items-start space-x-4"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<p class="font-semibold text-ghost-100">
|
<p class="font-semibold text-guild-100">
|
||||||
{{ speaker.name }}
|
{{ speaker.name }}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-ghost-300">
|
<p class="text-sm text-guild-300">
|
||||||
{{ speaker.role }}
|
{{ speaker.role }}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-ghost-400 mt-1">
|
<p class="text-sm text-guild-400 mt-1">
|
||||||
{{ speaker.bio }}
|
{{ speaker.bio }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -367,7 +367,7 @@
|
||||||
v-else-if="memberData && (!event.membersOnly || isMember)"
|
v-else-if="memberData && (!event.membersOnly || isMember)"
|
||||||
class="text-center"
|
class="text-center"
|
||||||
>
|
>
|
||||||
<p class="text-lg text-ghost-200 mb-6">
|
<p class="text-lg text-guild-200 mb-6">
|
||||||
You are logged in, {{ memberData.name }}.
|
You are logged in, {{ memberData.name }}.
|
||||||
</p>
|
</p>
|
||||||
<UButton
|
<UButton
|
||||||
|
|
@ -383,14 +383,14 @@
|
||||||
|
|
||||||
<!-- Not Logged In - Show Registration Form -->
|
<!-- Not Logged In - Show Registration Form -->
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<h3 class="text-xl font-bold text-ghost-100 mb-6">
|
<h3 class="text-xl font-bold text-guild-100 mb-6">
|
||||||
Register for This Event
|
Register for This Event
|
||||||
</h3>
|
</h3>
|
||||||
<form @submit.prevent="handleRegistration" class="space-y-4">
|
<form @submit.prevent="handleRegistration" class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
for="name"
|
for="name"
|
||||||
class="block text-sm font-medium text-ghost-200 mb-2"
|
class="block text-sm font-medium text-guild-200 mb-2"
|
||||||
>
|
>
|
||||||
Full Name
|
Full Name
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -406,7 +406,7 @@
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
for="email"
|
for="email"
|
||||||
class="block text-sm font-medium text-ghost-200 mb-2"
|
class="block text-sm font-medium text-guild-200 mb-2"
|
||||||
>
|
>
|
||||||
Email Address
|
Email Address
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -422,7 +422,7 @@
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
for="membershipLevel"
|
for="membershipLevel"
|
||||||
class="block text-sm font-medium text-ghost-200 mb-2"
|
class="block text-sm font-medium text-guild-200 mb-2"
|
||||||
>
|
>
|
||||||
Membership Status
|
Membership Status
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -452,17 +452,17 @@
|
||||||
<!-- Event Capacity -->
|
<!-- Event Capacity -->
|
||||||
<div
|
<div
|
||||||
v-if="event.maxAttendees"
|
v-if="event.maxAttendees"
|
||||||
class="mt-6 pt-6 border-t border-ghost-700"
|
class="mt-6 pt-6 border-t border-guild-700"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="text-sm text-ghost-300">Event Capacity</span>
|
<span class="text-sm text-guild-300">Event Capacity</span>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<span class="text-sm font-semibold text-ghost-100">
|
<span class="text-sm font-semibold text-guild-100">
|
||||||
{{ event.registeredCount || 0 }} /
|
{{ event.registeredCount || 0 }} /
|
||||||
{{ event.maxAttendees }}
|
{{ event.maxAttendees }}
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
class="w-24 h-2 bg-ghost-700 rounded-full overflow-hidden"
|
class="w-24 h-2 bg-guild-700 rounded-full overflow-hidden"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="h-full bg-blue-500 rounded-full"
|
class="h-full bg-blue-500 rounded-full"
|
||||||
|
|
@ -476,7 +476,7 @@
|
||||||
<!-- Waitlist Section -->
|
<!-- Waitlist Section -->
|
||||||
<div
|
<div
|
||||||
v-if="event.tickets?.waitlist?.enabled && isEventFull"
|
v-if="event.tickets?.waitlist?.enabled && isEventFull"
|
||||||
class="mt-6 pt-6 border-t border-ghost-700"
|
class="mt-6 pt-6 border-t border-guild-700"
|
||||||
>
|
>
|
||||||
<!-- Already on Waitlist -->
|
<!-- Already on Waitlist -->
|
||||||
<div v-if="isOnWaitlist" class="text-center">
|
<div v-if="isOnWaitlist" class="text-center">
|
||||||
|
|
@ -534,9 +534,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Additional Information -->
|
<!-- Additional Information -->
|
||||||
<div class="mt-8 p-6 rounded-xl border border-ghost-700">
|
<div class="mt-8 p-6 rounded-xl border border-guild-700">
|
||||||
<h4 class="font-semibold text-ghost-100 mb-3">Questions?</h4>
|
<h4 class="font-semibold text-guild-100 mb-3">Questions?</h4>
|
||||||
<p class="text-sm text-ghost-200 mb-3">
|
<p class="text-sm text-guild-200 mb-3">
|
||||||
If you have any questions about this event please drop us a line.
|
If you have any questions about this event please drop us a line.
|
||||||
</p>
|
</p>
|
||||||
<a
|
<a
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Events Section with Tabs -->
|
<!-- Events Section with Tabs -->
|
||||||
<section class="bg-ghost-900 dark:bg-ghost-950">
|
<section class="bg-guild-900 dark:bg-guild-950">
|
||||||
<UContainer>
|
<UContainer>
|
||||||
<UTabs
|
<UTabs
|
||||||
v-model="activeTab"
|
v-model="activeTab"
|
||||||
|
|
@ -51,7 +51,7 @@
|
||||||
<div
|
<div
|
||||||
class="flex flex-col items-center md:items-start gap-2"
|
class="flex flex-col items-center md:items-start gap-2"
|
||||||
>
|
>
|
||||||
<label class="text-sm font-medium text-ghost-200">
|
<label class="text-sm font-medium text-guild-200">
|
||||||
Show past events?
|
Show past events?
|
||||||
</label>
|
</label>
|
||||||
<USwitch v-model="includePastEvents" />
|
<USwitch v-model="includePastEvents" />
|
||||||
|
|
@ -79,10 +79,10 @@
|
||||||
|
|
||||||
<template #panel="{ close }">
|
<template #panel="{ close }">
|
||||||
<div
|
<div
|
||||||
class="bg-ghost-800 dark:bg-ghost-900 p-6 rounded-md shadow-lg w-80 space-y-4"
|
class="bg-guild-800 dark:bg-guild-900 p-6 rounded-md shadow-lg w-80 space-y-4"
|
||||||
>
|
>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="text-sm font-medium text-ghost-200">
|
<label class="text-sm font-medium text-guild-200">
|
||||||
Filter by category
|
Filter by category
|
||||||
</label>
|
</label>
|
||||||
<USelectMenu
|
<USelectMenu
|
||||||
|
|
@ -107,7 +107,7 @@
|
||||||
|
|
||||||
<!-- Search Status Message -->
|
<!-- Search Status Message -->
|
||||||
<div v-if="isSearching" class="mt-6 text-center">
|
<div v-if="isSearching" class="mt-6 text-center">
|
||||||
<p class="text-ghost-300 text-sm">
|
<p class="text-guild-300 text-sm">
|
||||||
{{
|
{{
|
||||||
filteredEvents.length > 0
|
filteredEvents.length > 0
|
||||||
? `Found ${filteredEvents.length} event${
|
? `Found ${filteredEvents.length} event${
|
||||||
|
|
@ -129,10 +129,10 @@
|
||||||
class="group flex items-start gap-4 py-2 hover:opacity-80 transition-opacity"
|
class="group flex items-start gap-4 py-2 hover:opacity-80 transition-opacity"
|
||||||
>
|
>
|
||||||
<div class="flex-shrink-0 text-center">
|
<div class="flex-shrink-0 text-center">
|
||||||
<div class="text-2xl font-bold text-ghost-100">
|
<div class="text-2xl font-bold text-guild-100">
|
||||||
{{ event.start.getDate() }}
|
{{ event.start.getDate() }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-ghost-400 uppercase">
|
<div class="text-xs text-guild-400 uppercase">
|
||||||
{{
|
{{
|
||||||
event.start.toLocaleDateString("en-US", {
|
event.start.toLocaleDateString("en-US", {
|
||||||
month: "short",
|
month: "short",
|
||||||
|
|
@ -144,7 +144,7 @@
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<div class="flex items-start gap-2 mb-1">
|
<div class="flex items-start gap-2 mb-1">
|
||||||
<h3
|
<h3
|
||||||
class="text-lg font-semibold text-ghost-100 group-hover:text-primary transition-colors"
|
class="text-lg font-semibold text-guild-100 group-hover:text-primary transition-colors"
|
||||||
>
|
>
|
||||||
{{ event.title }}
|
{{ event.title }}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
@ -155,7 +155,7 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-sm text-ghost-300 mb-2 line-clamp-2">
|
<p class="text-sm text-guild-300 mb-2 line-clamp-2">
|
||||||
{{ event.content }}
|
{{ event.content }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
@ -171,7 +171,7 @@
|
||||||
|
|
||||||
<Icon
|
<Icon
|
||||||
name="heroicons:arrow-right"
|
name="heroicons:arrow-right"
|
||||||
class="w-5 h-5 text-ghost-400 group-hover:text-primary group-hover:translate-x-1 transition-all flex-shrink-0 mt-1"
|
class="w-5 h-5 text-guild-400 group-hover:text-primary group-hover:translate-x-1 transition-all flex-shrink-0 mt-1"
|
||||||
/>
|
/>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -183,13 +183,13 @@
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<div
|
<div
|
||||||
v-if="pending"
|
v-if="pending"
|
||||||
class="min-h-[400px] bg-ghost-800 rounded-xl flex items-center justify-center"
|
class="min-h-[400px] bg-guild-800 rounded-xl flex items-center justify-center"
|
||||||
>
|
>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div
|
<div
|
||||||
class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"
|
class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"
|
||||||
></div>
|
></div>
|
||||||
<p class="text-ghost-200">Loading calendar...</p>
|
<p class="text-guild-200">Loading calendar...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else style="min-height: 600px">
|
<div v-else style="min-height: 600px">
|
||||||
|
|
@ -198,7 +198,7 @@
|
||||||
:events="calendarEvents"
|
:events="calendarEvents"
|
||||||
:time="false"
|
:time="false"
|
||||||
active-view="month"
|
active-view="month"
|
||||||
class="ghost-calendar"
|
class="guild-calendar"
|
||||||
:disable-views="['years', 'year']"
|
:disable-views="['years', 'year']"
|
||||||
:hide-view-selector="false"
|
:hide-view-selector="false"
|
||||||
events-on-month-view="short"
|
events-on-month-view="short"
|
||||||
|
|
@ -209,13 +209,13 @@
|
||||||
</div>
|
</div>
|
||||||
<template #fallback>
|
<template #fallback>
|
||||||
<div
|
<div
|
||||||
class="min-h-[400px] bg-ghost-800 rounded-xl flex items-center justify-center"
|
class="min-h-[400px] bg-guild-800 rounded-xl flex items-center justify-center"
|
||||||
>
|
>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div
|
<div
|
||||||
class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"
|
class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"
|
||||||
></div>
|
></div>
|
||||||
<p class="text-ghost-200">Loading calendar...</p>
|
<p class="text-guild-200">Loading calendar...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -229,7 +229,7 @@
|
||||||
|
|
||||||
<!-- Event Series -->
|
<!-- Event Series -->
|
||||||
<div v-if="activeSeries.length > 0" class="text-center my-12">
|
<div v-if="activeSeries.length > 0" class="text-center my-12">
|
||||||
<h2 class="text-3xl font-bold text-ghost-100 mb-8">
|
<h2 class="text-3xl font-bold text-guild-100 mb-8">
|
||||||
Current Event Series
|
Current Event Series
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -242,17 +242,17 @@
|
||||||
v-for="series in activeSeries.slice(0, 6)"
|
v-for="series in activeSeries.slice(0, 6)"
|
||||||
:key="series.id"
|
:key="series.id"
|
||||||
:to="`/series/${series.id}`"
|
:to="`/series/${series.id}`"
|
||||||
class="series-list-item block bg-ghost-800/50 dark:bg-ghost-700/30 rounded-xl p-6 border border-ghost-600 dark:border-ghost-600 hover:border-ghost-500 hover:bg-ghost-800/70 dark:hover:bg-ghost-700/50 transition-all duration-300"
|
class="series-list-item block bg-guild-800/50 dark:bg-guild-700/30 rounded-xl p-6 border border-guild-600 dark:border-guild-600 hover:border-guild-500 hover:bg-guild-800/70 dark:hover:bg-guild-700/50 transition-all duration-300"
|
||||||
>
|
>
|
||||||
<div class="flex items-start justify-between mb-4">
|
<div class="flex items-start justify-between mb-4">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span
|
<span
|
||||||
class="series-list-item__label text-sm font-semibold text-ghost-300 dark:text-ghost-300"
|
class="series-list-item__label text-sm font-semibold text-guild-300 dark:text-guild-300"
|
||||||
>
|
>
|
||||||
Event Series
|
Event Series
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="series-list-item__count inline-flex items-center px-2 py-0.5 rounded-md bg-ghost-700/50 dark:bg-ghost-600/50 text-sm font-medium text-ghost-200 dark:text-ghost-200"
|
class="series-list-item__count inline-flex items-center px-2 py-0.5 rounded-md bg-guild-700/50 dark:bg-guild-600/50 text-sm font-medium text-guild-200 dark:text-guild-200"
|
||||||
>
|
>
|
||||||
{{ series.eventCount }} events
|
{{ series.eventCount }} events
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -272,13 +272,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3
|
<h3
|
||||||
class="series-list-item__title text-lg font-semibold text-ghost-100 dark:text-ghost-100 mb-2"
|
class="series-list-item__title text-lg font-semibold text-guild-100 dark:text-guild-100 mb-2"
|
||||||
>
|
>
|
||||||
{{ series.title }}
|
{{ series.title }}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p
|
<p
|
||||||
class="series-list-item__description text-sm text-ghost-300 dark:text-ghost-300 mb-4 line-clamp-2"
|
class="series-list-item__description text-sm text-guild-300 dark:text-guild-300 mb-4 line-clamp-2"
|
||||||
>
|
>
|
||||||
{{ series.description }}
|
{{ series.description }}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -291,31 +291,31 @@
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div
|
<div
|
||||||
class="series-list-item__event-number w-6 h-6 bg-ghost-700/50 dark:bg-ghost-600/50 text-ghost-200 dark:text-ghost-200 rounded-full flex items-center justify-center text-xs font-medium border border-ghost-600 dark:border-ghost-500"
|
class="series-list-item__event-number w-6 h-6 bg-guild-700/50 dark:bg-guild-600/50 text-guild-200 dark:text-guild-200 rounded-full flex items-center justify-center text-xs font-medium border border-guild-600 dark:border-guild-500"
|
||||||
>
|
>
|
||||||
{{ event.series?.position || index + 1 }}
|
{{ event.series?.position || index + 1 }}
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
class="series-list-item__event-title text-ghost-200 dark:text-ghost-200 truncate"
|
class="series-list-item__event-title text-guild-200 dark:text-guild-200 truncate"
|
||||||
>{{ event.title }}</span
|
>{{ event.title }}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
class="series-list-item__event-date text-ghost-300 dark:text-ghost-300"
|
class="series-list-item__event-date text-guild-300 dark:text-guild-300"
|
||||||
>
|
>
|
||||||
{{ formatEventDate(event.startDate) }}
|
{{ formatEventDate(event.startDate) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="series.events.length > 3"
|
v-if="series.events.length > 3"
|
||||||
class="series-list-item__more-events text-xs text-ghost-300 dark:text-ghost-300 text-center pt-1"
|
class="series-list-item__more-events text-xs text-guild-300 dark:text-guild-300 text-center pt-1"
|
||||||
>
|
>
|
||||||
+{{ series.events.length - 3 }} more events
|
+{{ series.events.length - 3 }} more events
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="series-list-item__date-range text-sm text-ghost-300 dark:text-ghost-300"
|
class="series-list-item__date-range text-sm text-guild-300 dark:text-guild-300"
|
||||||
>
|
>
|
||||||
{{ formatDateRange(series.startDate, series.endDate) }}
|
{{ formatDateRange(series.startDate, series.endDate) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -323,27 +323,27 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Attend Our Events -->
|
<!-- Attend Our Events -->
|
||||||
<section class="py-20 bg-ghost-800 dark:bg-ghost-900">
|
<section class="py-20 bg-guild-800 dark:bg-guild-900">
|
||||||
<UContainer>
|
<UContainer>
|
||||||
<div class="text-center mb-16">
|
<div class="text-center mb-16">
|
||||||
<h2 class="text-3xl font-bold text-ghost-100 mb-8">
|
<h2 class="text-3xl font-bold text-guild-100 mb-8">
|
||||||
Attend Our Events
|
Attend Our Events
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="max-w-4xl mx-auto">
|
<div class="max-w-4xl mx-auto">
|
||||||
<div
|
<div
|
||||||
class="bg-ghost-900 rounded-2xl p-8 border border-ghost-700 mb-12"
|
class="bg-guild-900 rounded-2xl p-8 border border-guild-700 mb-12"
|
||||||
>
|
>
|
||||||
<div class="prose prose-lg dark:prose-invert max-w-none">
|
<div class="prose prose-lg dark:prose-invert max-w-none">
|
||||||
<p class="text-lg leading-relaxed text-ghost-200 mb-6">
|
<p class="text-lg leading-relaxed text-guild-200 mb-6">
|
||||||
Our events bring together game developers, founders, and practitioners
|
Our events bring together game developers, founders, and practitioners
|
||||||
who are building more equitable workplaces. From hands-on workshops
|
who are building more equitable workplaces. From hands-on workshops
|
||||||
about governance and finance to casual co-working sessions and game nights,
|
about governance and finance to casual co-working sessions and game nights,
|
||||||
there's something for every stage of your journey.
|
there's something for every stage of your journey.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="text-lg leading-relaxed text-ghost-200 mb-6">
|
<p class="text-lg leading-relaxed text-guild-200 mb-6">
|
||||||
All events are designed to be accessible, with most offered free to members
|
All events are designed to be accessible, with most offered free to members
|
||||||
and sliding-scale pricing for non-members. Can't make it live?
|
and sliding-scale pricing for non-members. Can't make it live?
|
||||||
Many sessions are recorded and shared in our resource library.
|
Many sessions are recorded and shared in our resource library.
|
||||||
|
|
@ -353,31 +353,31 @@
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h3 class="text-lg font-semibold text-ghost-100 mb-2">
|
<h3 class="text-lg font-semibold text-guild-100 mb-2">
|
||||||
Monthly Meetups
|
Monthly Meetups
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p class="text-sm text-ghost-300">
|
<p class="text-sm text-guild-300">
|
||||||
Casual knowledge sharing sessions
|
Casual knowledge sharing sessions
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h3 class="text-lg font-semibold text-ghost-100 mb-2">
|
<h3 class="text-lg font-semibold text-guild-100 mb-2">
|
||||||
Workshops
|
Workshops
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p class="text-sm text-ghost-300">
|
<p class="text-sm text-guild-300">
|
||||||
Hands-on learning about cooperative and worker-centric business
|
Hands-on learning about cooperative and worker-centric business
|
||||||
models
|
models
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h3 class="text-lg font-semibold text-ghost-100 mb-2">
|
<h3 class="text-lg font-semibold text-guild-100 mb-2">
|
||||||
Social Events
|
Social Events
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-ghost-300">
|
<p class="text-sm text-guild-300">
|
||||||
Game nights, socials, and more
|
Game nights, socials, and more
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -638,47 +638,47 @@ const getSeriesTypeBadgeClass = (type) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Calendar styling based on tranzac design principles */
|
/* Calendar styling based on tranzac design principles */
|
||||||
.ghost-calendar :deep(.vuecal__event) {
|
.guild-calendar :deep(.vuecal__event) {
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
border: 2px solid #292524;
|
border: 2px solid #292524;
|
||||||
transform: translateZ(0);
|
transform: translateZ(0);
|
||||||
transition: transform 300ms;
|
transition: transform 300ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__event:hover) {
|
.guild-calendar :deep(.vuecal__event:hover) {
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__event-title) {
|
.guild-calendar :deep(.vuecal__event-title) {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__event-time) {
|
.guild-calendar :deep(.vuecal__event-time) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__event.event-community) {
|
.guild-calendar :deep(.vuecal__event.event-community) {
|
||||||
background-color: #2563eb;
|
background-color: #2563eb;
|
||||||
color: #f5f5f4;
|
color: #f5f5f4;
|
||||||
border-color: #1d4ed8;
|
border-color: #1d4ed8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__event.event-workshop) {
|
.guild-calendar :deep(.vuecal__event.event-workshop) {
|
||||||
background-color: #059669;
|
background-color: #059669;
|
||||||
color: #f5f5f4;
|
color: #f5f5f4;
|
||||||
border-color: #047857;
|
border-color: #047857;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__event.event-social) {
|
.guild-calendar :deep(.vuecal__event.event-social) {
|
||||||
background-color: #7c3aed;
|
background-color: #7c3aed;
|
||||||
color: #f5f5f4;
|
color: #f5f5f4;
|
||||||
border-color: #6d28d9;
|
border-color: #6d28d9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__event.event-showcase) {
|
.guild-calendar :deep(.vuecal__event.event-showcase) {
|
||||||
background-color: #d97706;
|
background-color: #d97706;
|
||||||
color: #f5f5f4;
|
color: #f5f5f4;
|
||||||
border-color: #b45309;
|
border-color: #b45309;
|
||||||
|
|
@ -829,7 +829,7 @@ const getSeriesTypeBadgeClass = (type) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ghost calendar theme */
|
/* Ghost calendar theme */
|
||||||
.ghost-calendar {
|
.guild-calendar {
|
||||||
--vuecal-primary-color: #fff;
|
--vuecal-primary-color: #fff;
|
||||||
--vuecal-text-color: #e7e5e4;
|
--vuecal-text-color: #e7e5e4;
|
||||||
--vuecal-border-color: #57534e;
|
--vuecal-border-color: #57534e;
|
||||||
|
|
@ -838,120 +838,120 @@ const getSeriesTypeBadgeClass = (type) => {
|
||||||
background-color: #292524;
|
background-color: #292524;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__bg) {
|
.guild-calendar :deep(.vuecal__bg) {
|
||||||
background-color: #292524;
|
background-color: #292524;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__header) {
|
.guild-calendar :deep(.vuecal__header) {
|
||||||
background-color: #1c1917;
|
background-color: #1c1917;
|
||||||
border-bottom: 1px solid #57534e;
|
border-bottom: 1px solid #57534e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__title-bar) {
|
.guild-calendar :deep(.vuecal__title-bar) {
|
||||||
background-color: #1c1917;
|
background-color: #1c1917;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__title) {
|
.guild-calendar :deep(.vuecal__title) {
|
||||||
color: #e7e5e4;
|
color: #e7e5e4;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__weekdays-headings) {
|
.guild-calendar :deep(.vuecal__weekdays-headings) {
|
||||||
background-color: #1c1917;
|
background-color: #1c1917;
|
||||||
border-bottom: 1px solid #57534e;
|
border-bottom: 1px solid #57534e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__heading) {
|
.guild-calendar :deep(.vuecal__heading) {
|
||||||
color: #a8a29e;
|
color: #a8a29e;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__cell) {
|
.guild-calendar :deep(.vuecal__cell) {
|
||||||
background-color: #292524;
|
background-color: #292524;
|
||||||
border-color: #57534e;
|
border-color: #57534e;
|
||||||
color: #e7e5e4;
|
color: #e7e5e4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__cell:hover) {
|
.guild-calendar :deep(.vuecal__cell:hover) {
|
||||||
background-color: #44403c;
|
background-color: #44403c;
|
||||||
border-color: #78716c;
|
border-color: #78716c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__cell-content) {
|
.guild-calendar :deep(.vuecal__cell-content) {
|
||||||
color: #e7e5e4;
|
color: #e7e5e4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__cell--today) {
|
.guild-calendar :deep(.vuecal__cell--today) {
|
||||||
background-color: rgba(59, 130, 246, 0.1);
|
background-color: rgba(59, 130, 246, 0.1);
|
||||||
border: 2px solid #3b82f6;
|
border: 2px solid #3b82f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__cell--out-of-scope) {
|
.guild-calendar :deep(.vuecal__cell--out-of-scope) {
|
||||||
background-color: #1c1917;
|
background-color: #1c1917;
|
||||||
color: #78716c;
|
color: #78716c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__arrow) {
|
.guild-calendar :deep(.vuecal__arrow) {
|
||||||
color: #a8a29e;
|
color: #a8a29e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__arrow:hover) {
|
.guild-calendar :deep(.vuecal__arrow:hover) {
|
||||||
background-color: #44403c;
|
background-color: #44403c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__today-btn) {
|
.guild-calendar :deep(.vuecal__today-btn) {
|
||||||
background-color: #44403c;
|
background-color: #44403c;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border: 1px solid #78716c;
|
border: 1px solid #78716c;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__today-btn:hover) {
|
.guild-calendar :deep(.vuecal__today-btn:hover) {
|
||||||
background-color: #57534e;
|
background-color: #57534e;
|
||||||
border-color: #a8a29e;
|
border-color: #a8a29e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__view-btn),
|
.guild-calendar :deep(.vuecal__view-btn),
|
||||||
.ghost-calendar :deep(button[class*="view"]) {
|
.guild-calendar :deep(button[class*="view"]) {
|
||||||
background-color: #44403c !important;
|
background-color: #44403c !important;
|
||||||
color: #ffffff !important;
|
color: #ffffff !important;
|
||||||
border: 1px solid #78716c !important;
|
border: 1px solid #78716c !important;
|
||||||
font-weight: 600 !important;
|
font-weight: 600 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__view-btn:hover),
|
.guild-calendar :deep(.vuecal__view-btn:hover),
|
||||||
.ghost-calendar :deep(button[class*="view"]:hover) {
|
.guild-calendar :deep(button[class*="view"]:hover) {
|
||||||
background-color: #57534e !important;
|
background-color: #57534e !important;
|
||||||
border-color: #a8a29e !important;
|
border-color: #a8a29e !important;
|
||||||
color: #ffffff !important;
|
color: #ffffff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__view-btn--active),
|
.guild-calendar :deep(.vuecal__view-btn--active),
|
||||||
.ghost-calendar :deep(button[class*="view"][class*="active"]) {
|
.guild-calendar :deep(button[class*="view"][class*="active"]) {
|
||||||
background-color: #0c0a09 !important;
|
background-color: #0c0a09 !important;
|
||||||
color: #ffffff !important;
|
color: #ffffff !important;
|
||||||
border-color: #a8a29e !important;
|
border-color: #a8a29e !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__view-btn--active:hover),
|
.guild-calendar :deep(.vuecal__view-btn--active:hover),
|
||||||
.ghost-calendar :deep(button[class*="view"][class*="active"]:hover) {
|
.guild-calendar :deep(button[class*="view"][class*="active"]:hover) {
|
||||||
background-color: #1c1917 !important;
|
background-color: #1c1917 !important;
|
||||||
border-color: #d6d3d1 !important;
|
border-color: #d6d3d1 !important;
|
||||||
color: #ffffff !important;
|
color: #ffffff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__title-bar button) {
|
.guild-calendar :deep(.vuecal__title-bar button) {
|
||||||
color: #ffffff !important;
|
color: #ffffff !important;
|
||||||
font-weight: 600 !important;
|
font-weight: 600 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__title-bar .default-view-btn) {
|
.guild-calendar :deep(.vuecal__title-bar .default-view-btn) {
|
||||||
background-color: #44403c !important;
|
background-color: #44403c !important;
|
||||||
color: #ffffff !important;
|
color: #ffffff !important;
|
||||||
border: 1px solid #78716c !important;
|
border: 1px solid #78716c !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ghost-calendar :deep(.vuecal__title-bar .default-view-btn.active) {
|
.guild-calendar :deep(.vuecal__title-bar .default-view-btn.active) {
|
||||||
background-color: #0c0a09 !important;
|
background-color: #0c0a09 !important;
|
||||||
border-color: #a8a29e !important;
|
border-color: #a8a29e !important;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,109 +1,139 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="relative">
|
<div class="max-w-6xl mx-auto px-6 md:px-8">
|
||||||
<!-- Experimental Hero Section -->
|
<!-- Hero Section -->
|
||||||
<section class="mb-24">
|
<section class="py-16 md:py-24">
|
||||||
<div class="relative">
|
<div class="max-w-2xl">
|
||||||
<!-- Large artistic title -->
|
|
||||||
<h1
|
<h1
|
||||||
class="text-6xl md:text-8xl font-bold text-ghost-100 ethereal-text leading-tight mb-8"
|
class="text-4xl md:text-5xl font-light text-guild-100 leading-tight mb-2"
|
||||||
>
|
>
|
||||||
Become a Ghostie
|
Build your co-op studio
|
||||||
</h1>
|
</h1>
|
||||||
|
<p
|
||||||
|
class="text-4xl md:text-5xl font-light text-guild-500 leading-tight mb-8"
|
||||||
|
>
|
||||||
|
with people who get it.
|
||||||
|
</p>
|
||||||
|
|
||||||
<!-- Floating subtitle -->
|
<p class="text-lg text-guild-400 leading-relaxed mb-8 max-w-xl">
|
||||||
<div class="mb-16">
|
Ghost Guild is a peer community for game developers exploring
|
||||||
<p class="text-ghost-100 text-lg max-w-md">
|
cooperative models. Find support, share knowledge, grow together.
|
||||||
A peer community for creatives and game devs<br />
|
</p>
|
||||||
exploring cooperative models
|
|
||||||
|
<!-- Signup Form -->
|
||||||
|
<form @submit.prevent="handleJoinSubmit" class="mb-4">
|
||||||
|
<div class="flex flex-col sm:flex-row gap-3">
|
||||||
|
<UInput
|
||||||
|
v-model="joinEmail"
|
||||||
|
type="email"
|
||||||
|
placeholder="your.email@example.com"
|
||||||
|
size="lg"
|
||||||
|
class="flex-1"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
/>
|
||||||
|
<UButton
|
||||||
|
type="submit"
|
||||||
|
size="lg"
|
||||||
|
:loading="isSubmitting"
|
||||||
|
:disabled="!isEmailValid"
|
||||||
|
>
|
||||||
|
Join Us
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p class="text-sm text-guild-600">Free to join. Pay what you can.</p>
|
||||||
|
|
||||||
|
<!-- Success/Error Messages -->
|
||||||
|
<div
|
||||||
|
v-if="submitSuccess"
|
||||||
|
class="mt-4 p-3 bg-primary-500/10 border border-primary-500/30 rounded-lg"
|
||||||
|
>
|
||||||
|
<p class="text-primary-400 text-sm">
|
||||||
|
Check your email to complete signup!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Decorative elements -->
|
|
||||||
<div
|
<div
|
||||||
class="absolute top-0 right-0 w-32 h-32 border border-ghost-800 rounded-full opacity-20"
|
v-if="submitError"
|
||||||
/>
|
class="mt-4 p-3 bg-red-500/10 border border-red-500/30 rounded-lg"
|
||||||
<div
|
>
|
||||||
class="absolute top-20 -left-8 w-16 h-px bg-whisper-500 opacity-40"
|
<p class="text-red-400 text-sm">{{ submitError }}</p>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Join Section - Offset Layout -->
|
<!-- Value Props Section -->
|
||||||
<section class="mb-32 relative">
|
<section class="py-16 border-t border-guild-800">
|
||||||
<div>
|
<div class="grid md:grid-cols-3 gap-8 md:gap-12">
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium text-primary-400 mb-3">Peer Support</p>
|
||||||
|
<p class="text-guild-400 leading-relaxed">
|
||||||
|
Connect with founders at your stage and practitioners who've been
|
||||||
|
there. Real conversations, real help.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium text-primary-400 mb-3">
|
||||||
|
Shared Knowledge
|
||||||
|
</p>
|
||||||
|
<p class="text-guild-400 leading-relaxed">
|
||||||
|
Templates, governance docs, financial models—tools built by co-ops,
|
||||||
|
for co-ops. All members get full access.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium text-primary-400 mb-3">
|
||||||
|
Solidarity Economics
|
||||||
|
</p>
|
||||||
|
<p class="text-guild-400 leading-relaxed">
|
||||||
|
Those who can, support those who can't. No tiers, no gatekeeping.
|
||||||
|
Everyone gets everything.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Circles Section -->
|
||||||
|
<section class="py-16 border-t border-guild-800">
|
||||||
|
<p class="text-sm text-guild-600 mb-8">Find your people</p>
|
||||||
|
|
||||||
|
<div class="space-y-4 mb-8">
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
to="/join"
|
v-for="circle in circles"
|
||||||
class="inline-block px-8 py-3 border border-ghost-600 text-ghost-200 hover:bg-ghost-800 hover:border-whisper-500 hover:ethereal-text transition-all duration-500"
|
:key="circle.value"
|
||||||
|
to="/about/circles"
|
||||||
|
class="flex items-baseline gap-8 group py-2"
|
||||||
>
|
>
|
||||||
Join Us Today →
|
<span
|
||||||
|
class="text-guild-300 group-hover:text-guild-100 transition-colors w-32 md:w-40"
|
||||||
|
>
|
||||||
|
{{ circle.label }}
|
||||||
|
</span>
|
||||||
|
<span class="text-guild-600">
|
||||||
|
{{ circle.shortDescription }}
|
||||||
|
</span>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Decorative corner element -->
|
<p class="text-sm text-guild-600 italic">
|
||||||
<div
|
These reflect your journey, not your status. Move between them as you
|
||||||
class="absolute -right-4 top-0 w-20 h-20 border-t border-r border-ghost-800 opacity-30"
|
grow.
|
||||||
/>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Circles - Asymmetrical Grid -->
|
<!-- Bottom CTA Section -->
|
||||||
<section class="mb-32">
|
<section class="py-24 border-t border-guild-800 text-center">
|
||||||
<div class="space-y-8">
|
<p class="text-sm text-guild-600 mb-4">Part of the Baby Ghosts family</p>
|
||||||
<div
|
<h2 class="text-3xl md:text-4xl font-light text-guild-200 mb-8">
|
||||||
v-for="(circle, index) in circles"
|
Ready to find your people?
|
||||||
:key="circle.value"
|
</h2>
|
||||||
class="flex gap-8 items-start"
|
<UButton
|
||||||
>
|
to="/join"
|
||||||
<!-- Content -->
|
variant="outline"
|
||||||
<div class="flex-1 max-w-lg">
|
size="lg"
|
||||||
<h3 class="text-xl text-ghost-100 mb-3">{{ circle.label }}</h3>
|
class="hover:bg-primary-500/10"
|
||||||
<p class="text-ghost-200 text-sm leading-relaxed mb-4">
|
>
|
||||||
{{ circle.description }}
|
Become a Ghostie
|
||||||
</p>
|
</UButton>
|
||||||
|
|
||||||
<!-- Features as inline text -->
|
|
||||||
<div class="text-sm text-ghost-400">
|
|
||||||
<span v-for="(feature, i) in circle.features" :key="feature">
|
|
||||||
{{ feature
|
|
||||||
}}<span v-if="i < circle.features.length - 1"> • </span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Side accent -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Why Join? - Diagonal Layout -->
|
|
||||||
<section class="mb-32 relative">
|
|
||||||
<div class="transform -rotate-1">
|
|
||||||
<h2 class="text-3xl font-light text-ghost-200 mb-12">Why Join?</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ml-12 relative">
|
|
||||||
<div
|
|
||||||
class="absolute -left-4 top-0 w-32 h-px bg-whisper-500 opacity-30 transform rotate-12"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="max-w-2xl">
|
|
||||||
<p class="text-ghost-300 leading-loose text-lg mb-8">
|
|
||||||
Ghost Guild is Baby Ghosts' membership program, and a community of
|
|
||||||
game makers building studios that center workers, not just profits.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p class="text-ghost-400 leading-relaxed ml-8">
|
|
||||||
There's space for you no matter where you are in your cooperative
|
|
||||||
journey and no matter where in the world you are! You'll find peers,
|
|
||||||
resources, and support here.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="absolute -bottom-8 right-0 text-6xl text-ghost-800 opacity-20 font-bold"
|
|
||||||
>
|
|
||||||
?
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -111,5 +141,40 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { getCircleOptions } from "~/config/circles";
|
import { getCircleOptions } from "~/config/circles";
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
layout: "landing",
|
||||||
|
});
|
||||||
|
|
||||||
const circles = getCircleOptions();
|
const circles = getCircleOptions();
|
||||||
|
|
||||||
|
// Join form state
|
||||||
|
const joinEmail = ref("");
|
||||||
|
const isSubmitting = ref(false);
|
||||||
|
const submitSuccess = ref(false);
|
||||||
|
const submitError = ref("");
|
||||||
|
|
||||||
|
const isEmailValid = computed(() => {
|
||||||
|
return joinEmail.value && joinEmail.value.includes("@");
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleJoinSubmit = async () => {
|
||||||
|
if (!isEmailValid.value || isSubmitting.value) return;
|
||||||
|
|
||||||
|
isSubmitting.value = true;
|
||||||
|
submitSuccess.value = false;
|
||||||
|
submitError.value = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Redirect to join page with email pre-filled
|
||||||
|
await navigateTo({
|
||||||
|
path: "/join",
|
||||||
|
query: { email: joinEmail.value },
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Join error:", err);
|
||||||
|
submitError.value = "Something went wrong. Please try again.";
|
||||||
|
} finally {
|
||||||
|
isSubmitting.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,9 @@
|
||||||
>
|
>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div
|
<div
|
||||||
class="w-8 h-8 border-4 border-whisper-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"
|
class="w-8 h-8 border-4 border-candlelight-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"
|
||||||
/>
|
/>
|
||||||
<p class="text-ghost-300">Loading your dashboard...</p>
|
<p class="text-guild-300">Loading your dashboard...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -28,11 +28,11 @@
|
||||||
class="flex justify-center items-center py-20"
|
class="flex justify-center items-center py-20"
|
||||||
>
|
>
|
||||||
<div class="text-center max-w-md">
|
<div class="text-center max-w-md">
|
||||||
<div class="w-16 h-16 bg-ghost-800 border border-ghost-600 rounded-full flex items-center justify-center mx-auto mb-4">
|
<div class="w-16 h-16 bg-guild-800 border border-guild-600 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
<Icon name="heroicons:lock-closed" class="w-8 h-8 text-ghost-400" />
|
<Icon name="heroicons:lock-closed" class="w-8 h-8 text-guild-400" />
|
||||||
</div>
|
</div>
|
||||||
<h2 class="text-xl font-semibold text-ghost-100 mb-2">Sign in required</h2>
|
<h2 class="text-xl font-semibold text-guild-100 mb-2">Sign in required</h2>
|
||||||
<p class="text-ghost-400 mb-6">Please sign in to access your member dashboard.</p>
|
<p class="text-guild-400 mb-6">Please sign in to access your member dashboard.</p>
|
||||||
<UButton @click="openLoginModal({ title: 'Sign in to your dashboard', description: 'Enter your email to access your member dashboard' })">
|
<UButton @click="openLoginModal({ title: 'Sign in to your dashboard', description: 'Enter your email to access your member dashboard' })">
|
||||||
Sign In
|
Sign In
|
||||||
</UButton>
|
</UButton>
|
||||||
|
|
@ -45,23 +45,23 @@
|
||||||
<MemberStatusBanner :dismissible="true" />
|
<MemberStatusBanner :dismissible="true" />
|
||||||
<!-- Welcome Card -->
|
<!-- Welcome Card -->
|
||||||
<UCard
|
<UCard
|
||||||
class="sparkle-field"
|
|
||||||
:ui="{
|
:ui="{
|
||||||
root: 'bg-ghost-900 border border-ghost-700',
|
root: 'bg-guild-900 border border-guild-700',
|
||||||
header: 'border-b border-ghost-700',
|
header: 'border-b border-guild-700',
|
||||||
body: 'bg-ghost-900',
|
body: 'bg-guild-900',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-start justify-between gap-4">
|
<div class="flex items-start justify-between gap-4">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<h1 class="text-2xl font-bold text-ghost-100 ethereal-text">
|
<h1 class="text-2xl font-bold text-guild-100 warm-text">
|
||||||
Welcome to Ghost Guild, {{ memberData?.name }}!
|
Welcome to Ghost Guild, {{ memberData?.name }}!
|
||||||
</h1>
|
</h1>
|
||||||
<p
|
<p
|
||||||
:class="[
|
:class="[
|
||||||
'mt-2',
|
'mt-2',
|
||||||
isActive ? 'text-ghost-300' : statusConfig.textColor,
|
isActive ? 'text-guild-300' : statusConfig.textColor,
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
|
|
@ -78,7 +78,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex-shrink-0">
|
<div v-else class="flex-shrink-0">
|
||||||
<div
|
<div
|
||||||
class="w-16 h-16 bg-ghost-700 border border-ghost-600 flex items-center justify-center text-ghost-200 font-bold text-xl"
|
class="w-16 h-16 bg-guild-700 border border-guild-600 flex items-center justify-center text-guild-200 font-bold text-xl"
|
||||||
>
|
>
|
||||||
{{ memberData?.name?.charAt(0)?.toUpperCase() }}
|
{{ memberData?.name?.charAt(0)?.toUpperCase() }}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -87,14 +87,14 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-4 text-sm">
|
<div class="flex flex-wrap gap-4 text-sm">
|
||||||
<div class="bg-ghost-800 border border-ghost-600 px-4 py-2">
|
<div class="bg-guild-800 border border-guild-600 px-4 py-2">
|
||||||
<span class="text-ghost-200">Circle:</span>
|
<span class="text-guild-200">Circle:</span>
|
||||||
<span class="font-medium text-stone-50 ml-1 capitalize">{{
|
<span class="font-medium text-stone-50 ml-1 capitalize">{{
|
||||||
memberData?.circle
|
memberData?.circle
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-ghost-800 border border-ghost-600 px-4 py-2">
|
<div class="bg-guild-800 border border-guild-600 px-4 py-2">
|
||||||
<span class="text-ghost-200">Contribution:</span>
|
<span class="text-guild-200">Contribution:</span>
|
||||||
<span class="font-medium text-stone-50 ml-1"
|
<span class="font-medium text-stone-50 ml-1"
|
||||||
>${{ memberData?.contributionTier }} CAD/month</span
|
>${{ memberData?.contributionTier }} CAD/month</span
|
||||||
>
|
>
|
||||||
|
|
@ -105,13 +105,13 @@
|
||||||
<!-- Quick Links -->
|
<!-- Quick Links -->
|
||||||
<UCard
|
<UCard
|
||||||
:ui="{
|
:ui="{
|
||||||
root: 'bg-ghost-900 border border-ghost-700',
|
root: 'bg-guild-900 border border-guild-700',
|
||||||
header: 'border-b border-ghost-700 bg-ghost-900',
|
header: 'border-b border-guild-700 bg-guild-900',
|
||||||
body: 'bg-ghost-900',
|
body: 'bg-guild-900',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<h2 class="text-xl font-bold text-ghost-100 ethereal-text">
|
<h2 class="text-xl font-bold text-guild-100 warm-text">
|
||||||
Quick Links
|
Quick Links
|
||||||
</h2>
|
</h2>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -122,9 +122,9 @@
|
||||||
variant="outline"
|
variant="outline"
|
||||||
:disabled="!canPeerSupport"
|
:disabled="!canPeerSupport"
|
||||||
:class="[
|
:class="[
|
||||||
'border-ghost-600 text-ghost-200 justify-start',
|
'border-guild-600 text-guild-200 justify-start',
|
||||||
canPeerSupport
|
canPeerSupport
|
||||||
? 'hover:bg-ghost-800 hover:border-whisper-500'
|
? 'hover:bg-guild-800 hover:border-candlelight-500'
|
||||||
: 'opacity-50 cursor-not-allowed',
|
: 'opacity-50 cursor-not-allowed',
|
||||||
]"
|
]"
|
||||||
block
|
block
|
||||||
|
|
@ -141,7 +141,7 @@
|
||||||
to="https://wiki.ghostguild.org"
|
to="https://wiki.ghostguild.org"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="border-ghost-600 text-ghost-200 hover:bg-ghost-800 hover:border-whisper-500 justify-start"
|
class="border-guild-600 text-guild-200 hover:bg-guild-800 hover:border-candlelight-500 justify-start"
|
||||||
block
|
block
|
||||||
>
|
>
|
||||||
Browse Resources
|
Browse Resources
|
||||||
|
|
@ -150,7 +150,7 @@
|
||||||
<UButton
|
<UButton
|
||||||
to="/member/profile"
|
to="/member/profile"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="border-ghost-600 text-ghost-200 hover:bg-ghost-800 hover:border-whisper-500 justify-start"
|
class="border-guild-600 text-guild-200 hover:bg-guild-800 hover:border-candlelight-500 justify-start"
|
||||||
block
|
block
|
||||||
>
|
>
|
||||||
Update Profile
|
Update Profile
|
||||||
|
|
@ -159,7 +159,7 @@
|
||||||
<UButton
|
<UButton
|
||||||
to="/events"
|
to="/events"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="border-ghost-600 text-ghost-200 hover:bg-ghost-800 hover:border-whisper-500 justify-start"
|
class="border-guild-600 text-guild-200 hover:bg-guild-800 hover:border-candlelight-500 justify-start"
|
||||||
block
|
block
|
||||||
>
|
>
|
||||||
View Events
|
View Events
|
||||||
|
|
@ -168,7 +168,7 @@
|
||||||
<UButton
|
<UButton
|
||||||
to="/members"
|
to="/members"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="border-ghost-600 text-ghost-200 hover:bg-ghost-800 hover:border-whisper-500 justify-start"
|
class="border-guild-600 text-guild-200 hover:bg-guild-800 hover:border-candlelight-500 justify-start"
|
||||||
block
|
block
|
||||||
>
|
>
|
||||||
Browse Members
|
Browse Members
|
||||||
|
|
@ -177,7 +177,7 @@
|
||||||
<UButton
|
<UButton
|
||||||
to="/member/profile#account"
|
to="/member/profile#account"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="border-ghost-600 text-ghost-200 hover:bg-ghost-800 hover:border-whisper-500 justify-start"
|
class="border-guild-600 text-guild-200 hover:bg-guild-800 hover:border-candlelight-500 justify-start"
|
||||||
block
|
block
|
||||||
>
|
>
|
||||||
Manage Account
|
Manage Account
|
||||||
|
|
@ -188,14 +188,14 @@
|
||||||
<!-- Your Registered Events -->
|
<!-- Your Registered Events -->
|
||||||
<UCard
|
<UCard
|
||||||
:ui="{
|
:ui="{
|
||||||
root: 'bg-ghost-900 border border-ghost-700',
|
root: 'bg-guild-900 border border-guild-700',
|
||||||
header: 'border-b border-ghost-700 bg-ghost-900',
|
header: 'border-b border-guild-700 bg-guild-900',
|
||||||
body: 'bg-ghost-900',
|
body: 'bg-guild-900',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h2 class="text-xl font-bold text-ghost-100 ethereal-text">
|
<h2 class="text-xl font-bold text-guild-100 warm-text">
|
||||||
Your Upcoming Events
|
Your Upcoming Events
|
||||||
</h2>
|
</h2>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
|
@ -204,7 +204,7 @@
|
||||||
@click="copyCalendarLink"
|
@click="copyCalendarLink"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
class="text-ghost-300 hover:text-ghost-100"
|
class="text-guild-300 hover:text-guild-100"
|
||||||
icon="heroicons:calendar"
|
icon="heroicons:calendar"
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
|
|
@ -215,7 +215,7 @@
|
||||||
to="/events"
|
to="/events"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
class="text-ghost-300 hover:text-ghost-100"
|
class="text-guild-300 hover:text-guild-100"
|
||||||
>
|
>
|
||||||
Browse All Events
|
Browse All Events
|
||||||
</UButton>
|
</UButton>
|
||||||
|
|
@ -225,7 +225,7 @@
|
||||||
|
|
||||||
<div v-if="loadingEvents" class="text-center py-8">
|
<div v-if="loadingEvents" class="text-center py-8">
|
||||||
<div
|
<div
|
||||||
class="w-6 h-6 border-2 border-whisper-500 border-t-transparent rounded-full animate-spin mx-auto"
|
class="w-6 h-6 border-2 border-candlelight-500 border-t-transparent rounded-full animate-spin mx-auto"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -234,7 +234,7 @@
|
||||||
v-for="evt in registeredEvents"
|
v-for="evt in registeredEvents"
|
||||||
:key="evt._id"
|
:key="evt._id"
|
||||||
:to="`/events/${evt.slug || evt._id}`"
|
:to="`/events/${evt.slug || evt._id}`"
|
||||||
class="block p-4 border border-ghost-700 hover:border-whisper-500 transition-colors"
|
class="block p-4 border border-guild-700 hover:border-candlelight-500 transition-colors"
|
||||||
>
|
>
|
||||||
<div class="flex items-start gap-4">
|
<div class="flex items-start gap-4">
|
||||||
<div
|
<div
|
||||||
|
|
@ -252,18 +252,18 @@
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="flex-shrink-0 w-20 h-20 bg-ghost-800 border border-ghost-600 flex items-center justify-center"
|
class="flex-shrink-0 w-20 h-20 bg-guild-800 border border-guild-600 flex items-center justify-center"
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
name="heroicons:calendar-days"
|
name="heroicons:calendar-days"
|
||||||
class="w-8 h-8 text-whisper-400"
|
class="w-8 h-8 text-candlelight-400"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<h3 class="font-semibold text-ghost-100 mb-1">
|
<h3 class="font-semibold text-guild-100 mb-1">
|
||||||
{{ evt.title }}
|
{{ evt.title }}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="flex items-center gap-4 text-sm text-ghost-400">
|
<div class="flex items-center gap-4 text-sm text-guild-400">
|
||||||
<span class="flex items-center gap-1">
|
<span class="flex items-center gap-1">
|
||||||
<Icon name="heroicons:calendar" class="w-4 h-4" />
|
<Icon name="heroicons:calendar" class="w-4 h-4" />
|
||||||
{{ formatEventDate(evt.startDate) }}
|
{{ formatEventDate(evt.startDate) }}
|
||||||
|
|
@ -277,7 +277,7 @@
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<Icon
|
<Icon
|
||||||
name="heroicons:chevron-right"
|
name="heroicons:chevron-right"
|
||||||
class="w-5 h-5 text-ghost-500"
|
class="w-5 h-5 text-guild-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -287,15 +287,15 @@
|
||||||
<div v-else class="text-center py-8">
|
<div v-else class="text-center py-8">
|
||||||
<Icon
|
<Icon
|
||||||
name="heroicons:calendar-days"
|
name="heroicons:calendar-days"
|
||||||
class="w-12 h-12 text-ghost-600 mx-auto mb-3"
|
class="w-12 h-12 text-guild-600 mx-auto mb-3"
|
||||||
/>
|
/>
|
||||||
<p class="text-ghost-400 mb-4">
|
<p class="text-guild-400 mb-4">
|
||||||
You haven't registered for any upcoming events
|
You haven't registered for any upcoming events
|
||||||
</p>
|
</p>
|
||||||
<UButton
|
<UButton
|
||||||
to="/events"
|
to="/events"
|
||||||
size="sm"
|
size="sm"
|
||||||
class="border-ghost-600 text-ghost-200 hover:bg-ghost-800 hover:border-whisper-500"
|
class="border-guild-600 text-guild-200 hover:bg-guild-800 hover:border-candlelight-500"
|
||||||
>
|
>
|
||||||
Browse Events
|
Browse Events
|
||||||
</UButton>
|
</UButton>
|
||||||
|
|
@ -304,15 +304,15 @@
|
||||||
<!-- Calendar subscription instructions -->
|
<!-- Calendar subscription instructions -->
|
||||||
<div
|
<div
|
||||||
v-if="registeredEvents.length > 0 && showCalendarInstructions"
|
v-if="registeredEvents.length > 0 && showCalendarInstructions"
|
||||||
class="mt-4 p-4 bg-ghost-800 border border-ghost-600"
|
class="mt-4 p-4 bg-guild-800 border border-guild-600"
|
||||||
>
|
>
|
||||||
<div class="flex items-start justify-between gap-4">
|
<div class="flex items-start justify-between gap-4">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<h4 class="text-sm font-semibold text-ghost-100 mb-2">
|
<h4 class="text-sm font-semibold text-guild-100 mb-2">
|
||||||
How to Subscribe to Your Calendar
|
How to Subscribe to Your Calendar
|
||||||
</h4>
|
</h4>
|
||||||
<ul
|
<ul
|
||||||
class="text-xs text-ghost-300 space-y-1 list-disc list-inside"
|
class="text-xs text-guild-300 space-y-1 list-disc list-inside"
|
||||||
>
|
>
|
||||||
<li>
|
<li>
|
||||||
<strong>Google Calendar:</strong> Click "+" → "From URL" →
|
<strong>Google Calendar:</strong> Click "+" → "From URL" →
|
||||||
|
|
@ -327,14 +327,14 @@
|
||||||
→ Paste the link
|
→ Paste the link
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="text-xs text-ghost-400 mt-2">
|
<p class="text-xs text-guild-400 mt-2">
|
||||||
Your calendar will automatically update when you register or
|
Your calendar will automatically update when you register or
|
||||||
unregister from events.
|
unregister from events.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@click="showCalendarInstructions = false"
|
@click="showCalendarInstructions = false"
|
||||||
class="text-ghost-400 hover:text-ghost-200"
|
class="text-guild-400 hover:text-guild-200"
|
||||||
>
|
>
|
||||||
<Icon name="heroicons:x-mark" class="w-5 h-5" />
|
<Icon name="heroicons:x-mark" class="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@
|
||||||
<UContainer class="px-4">
|
<UContainer class="px-4">
|
||||||
<!-- Stats -->
|
<!-- Stats -->
|
||||||
<div v-if="isAuthenticated && !pending" class="mb-8 flex items-center justify-between">
|
<div v-if="isAuthenticated && !pending" class="mb-8 flex items-center justify-between">
|
||||||
<div class="text-ghost-300">
|
<div class="text-guild-300">
|
||||||
<span class="text-2xl font-bold text-ghost-100">{{ total }}</span>
|
<span class="text-2xl font-bold text-guild-100">{{ total }}</span>
|
||||||
{{ total === 1 ? "update" : "updates" }} posted
|
{{ total === 1 ? "update" : "updates" }} posted
|
||||||
</div>
|
</div>
|
||||||
<UButton to="/updates/new" icon="i-lucide-plus"> New Update </UButton>
|
<UButton to="/updates/new" icon="i-lucide-plus"> New Update </UButton>
|
||||||
|
|
@ -25,9 +25,9 @@
|
||||||
>
|
>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div
|
<div
|
||||||
class="w-8 h-8 border-4 border-ghost-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"
|
class="w-8 h-8 border-4 border-guild-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"
|
||||||
></div>
|
></div>
|
||||||
<p class="text-ghost-400">Loading your updates...</p>
|
<p class="text-guild-400">Loading your updates...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -37,11 +37,11 @@
|
||||||
class="flex justify-center items-center py-20"
|
class="flex justify-center items-center py-20"
|
||||||
>
|
>
|
||||||
<div class="text-center max-w-md">
|
<div class="text-center max-w-md">
|
||||||
<div class="w-16 h-16 bg-ghost-800 border border-ghost-600 rounded-full flex items-center justify-center mx-auto mb-4">
|
<div class="w-16 h-16 bg-guild-800 border border-guild-600 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
<Icon name="heroicons:lock-closed" class="w-8 h-8 text-ghost-400" />
|
<Icon name="heroicons:lock-closed" class="w-8 h-8 text-guild-400" />
|
||||||
</div>
|
</div>
|
||||||
<h2 class="text-xl font-semibold text-ghost-100 mb-2">Sign in required</h2>
|
<h2 class="text-xl font-semibold text-guild-100 mb-2">Sign in required</h2>
|
||||||
<p class="text-ghost-400 mb-6">Please sign in to view your updates.</p>
|
<p class="text-guild-400 mb-6">Please sign in to view your updates.</p>
|
||||||
<UButton @click="openLoginModal({ title: 'Sign in to view your updates', description: 'Enter your email to access your updates' })">
|
<UButton @click="openLoginModal({ title: 'Sign in to view your updates', description: 'Enter your email to access your updates' })">
|
||||||
Sign In
|
Sign In
|
||||||
</UButton>
|
</UButton>
|
||||||
|
|
@ -79,7 +79,7 @@
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
class="text-ghost-600"
|
class="text-guild-600"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
|
|
@ -89,10 +89,10 @@
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-medium text-ghost-300 mb-2">
|
<h3 class="text-lg font-medium text-guild-300 mb-2">
|
||||||
No updates yet
|
No updates yet
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-ghost-400 mb-6">
|
<p class="text-guild-400 mb-6">
|
||||||
Share your first update with the community
|
Share your first update with the community
|
||||||
</p>
|
</p>
|
||||||
<UButton to="/updates/new" icon="i-lucide-plus">
|
<UButton to="/updates/new" icon="i-lucide-plus">
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
<div
|
<div
|
||||||
class="w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"
|
class="w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"
|
||||||
></div>
|
></div>
|
||||||
<p class="text-gray-600 dark:text-ghost-400">
|
<p class="text-gray-600 dark:text-guild-400">
|
||||||
Loading your profile...
|
Loading your profile...
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -30,11 +30,11 @@
|
||||||
class="flex justify-center items-center py-20"
|
class="flex justify-center items-center py-20"
|
||||||
>
|
>
|
||||||
<div class="text-center max-w-md">
|
<div class="text-center max-w-md">
|
||||||
<div class="w-16 h-16 bg-ghost-800 border border-ghost-600 rounded-full flex items-center justify-center mx-auto mb-4">
|
<div class="w-16 h-16 bg-guild-800 border border-guild-600 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
<Icon name="heroicons:lock-closed" class="w-8 h-8 text-ghost-400" />
|
<Icon name="heroicons:lock-closed" class="w-8 h-8 text-guild-400" />
|
||||||
</div>
|
</div>
|
||||||
<h2 class="text-xl font-semibold text-ghost-100 mb-2">Sign in required</h2>
|
<h2 class="text-xl font-semibold text-guild-100 mb-2">Sign in required</h2>
|
||||||
<p class="text-ghost-400 mb-6">Please sign in to access your profile settings.</p>
|
<p class="text-guild-400 mb-6">Please sign in to access your profile settings.</p>
|
||||||
<UButton @click="openLoginModal({ title: 'Sign in to your profile', description: 'Enter your email to manage your profile settings' })">
|
<UButton @click="openLoginModal({ title: 'Sign in to your profile', description: 'Enter your email to manage your profile settings' })">
|
||||||
Sign In
|
Sign In
|
||||||
</UButton>
|
</UButton>
|
||||||
|
|
@ -52,7 +52,7 @@
|
||||||
<!-- Basic Information -->
|
<!-- Basic Information -->
|
||||||
<div>
|
<div>
|
||||||
<h2
|
<h2
|
||||||
class="text-2xl font-semibold mb-8 text-gray-900 dark:text-ghost-100 ethereal-text"
|
class="text-2xl font-semibold mb-8 text-gray-900 dark:text-guild-100 warm-text"
|
||||||
>
|
>
|
||||||
Basic Information
|
Basic Information
|
||||||
</h2>
|
</h2>
|
||||||
|
|
@ -124,7 +124,7 @@
|
||||||
:class="
|
:class="
|
||||||
formData.avatar === ghost.value
|
formData.avatar === ghost.value
|
||||||
? 'border-blue-400 bg-blue-500/20'
|
? 'border-blue-400 bg-blue-500/20'
|
||||||
: 'border-ghost-700 bg-ghost-800/50 hover:border-ghost-600'
|
: 'border-guild-700 bg-guild-800/50 hover:border-guild-600'
|
||||||
"
|
"
|
||||||
@click="formData.avatar = ghost.value"
|
@click="formData.avatar = ghost.value"
|
||||||
>
|
>
|
||||||
|
|
@ -153,7 +153,7 @@
|
||||||
<!-- Professional Info -->
|
<!-- Professional Info -->
|
||||||
<div>
|
<div>
|
||||||
<h2
|
<h2
|
||||||
class="text-2xl font-semibold mb-8 text-gray-900 dark:text-ghost-100 ethereal-text"
|
class="text-2xl font-semibold mb-8 text-gray-900 dark:text-guild-100 warm-text"
|
||||||
>
|
>
|
||||||
Professional Information
|
Professional Information
|
||||||
</h2>
|
</h2>
|
||||||
|
|
@ -222,7 +222,7 @@
|
||||||
<!-- Community Connections -->
|
<!-- Community Connections -->
|
||||||
<div>
|
<div>
|
||||||
<h2
|
<h2
|
||||||
class="text-2xl font-semibold mb-8 text-gray-900 dark:text-ghost-100 ethereal-text"
|
class="text-2xl font-semibold mb-8 text-gray-900 dark:text-guild-100 warm-text"
|
||||||
>
|
>
|
||||||
Community Connections
|
Community Connections
|
||||||
</h2>
|
</h2>
|
||||||
|
|
@ -238,7 +238,7 @@
|
||||||
<!-- Tags input -->
|
<!-- Tags input -->
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
class="block text-sm font-medium text-gray-800 dark:text-ghost-200 mb-2"
|
class="block text-sm font-medium text-gray-800 dark:text-guild-200 mb-2"
|
||||||
>
|
>
|
||||||
Skills & Topics
|
Skills & Topics
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -270,7 +270,7 @@
|
||||||
<!-- Description textarea -->
|
<!-- Description textarea -->
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
class="block text-sm font-medium text-gray-800 dark:text-ghost-200 mb-2"
|
class="block text-sm font-medium text-gray-800 dark:text-guild-200 mb-2"
|
||||||
>
|
>
|
||||||
Details
|
Details
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -300,7 +300,7 @@
|
||||||
<!-- Tags input -->
|
<!-- Tags input -->
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
class="block text-sm font-medium text-gray-800 dark:text-ghost-200 mb-2"
|
class="block text-sm font-medium text-gray-800 dark:text-guild-200 mb-2"
|
||||||
>
|
>
|
||||||
Skills & Topics
|
Skills & Topics
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -332,7 +332,7 @@
|
||||||
<!-- Description textarea -->
|
<!-- Description textarea -->
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
class="block text-sm font-medium text-gray-800 dark:text-ghost-200 mb-2"
|
class="block text-sm font-medium text-gray-800 dark:text-guild-200 mb-2"
|
||||||
>
|
>
|
||||||
Details
|
Details
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -357,7 +357,7 @@
|
||||||
<!-- Peer Support -->
|
<!-- Peer Support -->
|
||||||
<div>
|
<div>
|
||||||
<h2
|
<h2
|
||||||
class="text-2xl font-semibold mb-8 text-gray-900 dark:text-ghost-100 ethereal-text"
|
class="text-2xl font-semibold mb-8 text-gray-900 dark:text-guild-100 warm-text"
|
||||||
>
|
>
|
||||||
Peer Support
|
Peer Support
|
||||||
</h2>
|
</h2>
|
||||||
|
|
@ -368,12 +368,12 @@
|
||||||
<USwitch v-model="formData.peerSupportEnabled" />
|
<USwitch v-model="formData.peerSupportEnabled" />
|
||||||
<div>
|
<div>
|
||||||
<p
|
<p
|
||||||
class="font-medium text-gray-800 dark:text-ghost-200"
|
class="font-medium text-gray-800 dark:text-guild-200"
|
||||||
>
|
>
|
||||||
Offer Peer Support
|
Offer Peer Support
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p
|
||||||
class="text-sm text-gray-600 dark:text-ghost-400 mt-1"
|
class="text-sm text-gray-600 dark:text-guild-400 mt-1"
|
||||||
>
|
>
|
||||||
Make yourself available to support other members
|
Make yourself available to support other members
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -478,7 +478,7 @@
|
||||||
/>
|
/>
|
||||||
<template #hint>
|
<template #hint>
|
||||||
<span
|
<span
|
||||||
class="text-xs text-gray-500 dark:text-ghost-500"
|
class="text-xs text-gray-500 dark:text-guild-500"
|
||||||
>
|
>
|
||||||
{{ formData.peerSupportMessage?.length || 0 }}/200
|
{{ formData.peerSupportMessage?.length || 0 }}/200
|
||||||
characters
|
characters
|
||||||
|
|
@ -506,7 +506,7 @@
|
||||||
<!-- Directory Settings -->
|
<!-- Directory Settings -->
|
||||||
<div>
|
<div>
|
||||||
<h2
|
<h2
|
||||||
class="text-2xl font-semibold mb-8 text-gray-900 dark:text-ghost-100 ethereal-text"
|
class="text-2xl font-semibold mb-8 text-gray-900 dark:text-guild-100 warm-text"
|
||||||
>
|
>
|
||||||
Directory Visibility
|
Directory Visibility
|
||||||
</h2>
|
</h2>
|
||||||
|
|
@ -514,10 +514,10 @@
|
||||||
<div class="flex items-start gap-4">
|
<div class="flex items-start gap-4">
|
||||||
<USwitch v-model="formData.showInDirectory" />
|
<USwitch v-model="formData.showInDirectory" />
|
||||||
<div>
|
<div>
|
||||||
<p class="font-medium text-gray-800 dark:text-ghost-200">
|
<p class="font-medium text-gray-800 dark:text-guild-200">
|
||||||
Show in Member Directory
|
Show in Member Directory
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-gray-600 dark:text-ghost-400 mt-1">
|
<p class="text-sm text-gray-600 dark:text-guild-400 mt-1">
|
||||||
Allow other members to discover and connect with you
|
Allow other members to discover and connect with you
|
||||||
through the directory
|
through the directory
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -544,7 +544,7 @@
|
||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<div
|
<div
|
||||||
class="flex justify-between items-center pt-4 border-t border-ghost-800/50"
|
class="flex justify-between items-center pt-4 border-t border-guild-800/50"
|
||||||
>
|
>
|
||||||
<UButton
|
<UButton
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -574,20 +574,20 @@
|
||||||
<!-- Current Membership -->
|
<!-- Current Membership -->
|
||||||
<div>
|
<div>
|
||||||
<h2
|
<h2
|
||||||
class="text-2xl font-semibold mb-6 text-gray-900 dark:text-ghost-100 ethereal-text"
|
class="text-2xl font-semibold mb-6 text-gray-900 dark:text-guild-100 warm-text"
|
||||||
>
|
>
|
||||||
Current Membership
|
Current Membership
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="backdrop-blur-sm bg-white/80 dark:bg-ghost-800/50 border border-gray-200 dark:border-ghost-700 rounded-lg p-6 space-y-4"
|
class="backdrop-blur-sm bg-white/80 dark:bg-guild-800/50 border border-gray-200 dark:border-guild-700 rounded-lg p-6 space-y-4"
|
||||||
>
|
>
|
||||||
<!-- Status Badge -->
|
<!-- Status Badge -->
|
||||||
<div
|
<div
|
||||||
class="flex items-center justify-between pb-4 border-b border-gray-200 dark:border-ghost-700"
|
class="flex items-center justify-between pb-4 border-b border-gray-200 dark:border-guild-700"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm text-gray-600 dark:text-ghost-400">
|
<p class="text-sm text-gray-600 dark:text-guild-400">
|
||||||
Membership Status
|
Membership Status
|
||||||
</p>
|
</p>
|
||||||
<div class="flex items-center gap-2 mt-1">
|
<div class="flex items-center gap-2 mt-1">
|
||||||
|
|
@ -620,21 +620,21 @@
|
||||||
|
|
||||||
<div class="flex items-start justify-between">
|
<div class="flex items-start justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm text-gray-600 dark:text-ghost-400">
|
<p class="text-sm text-gray-600 dark:text-guild-400">
|
||||||
Circle
|
Circle
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p
|
||||||
class="text-lg font-medium text-gray-900 dark:text-ghost-100 capitalize"
|
class="text-lg font-medium text-gray-900 dark:text-guild-100 capitalize"
|
||||||
>
|
>
|
||||||
{{ memberData.circle }}
|
{{ memberData.circle }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm text-gray-600 dark:text-ghost-400">
|
<p class="text-sm text-gray-600 dark:text-guild-400">
|
||||||
Contribution Level
|
Contribution Level
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p
|
||||||
class="text-lg font-medium text-gray-900 dark:text-ghost-100"
|
class="text-lg font-medium text-gray-900 dark:text-guild-100"
|
||||||
>
|
>
|
||||||
${{ contributionTierDetails?.amount }}/month
|
${{ contributionTierDetails?.amount }}/month
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -642,10 +642,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="memberData.subscriptionStartDate">
|
<div v-if="memberData.subscriptionStartDate">
|
||||||
<p class="text-sm text-gray-600 dark:text-ghost-400">
|
<p class="text-sm text-gray-600 dark:text-guild-400">
|
||||||
Member Since
|
Member Since
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-900 dark:text-ghost-100">
|
<p class="text-gray-900 dark:text-guild-100">
|
||||||
{{ formatDate(memberData.subscriptionStartDate) }}
|
{{ formatDate(memberData.subscriptionStartDate) }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -656,10 +656,10 @@
|
||||||
memberData.contributionTier !== '0'
|
memberData.contributionTier !== '0'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<p class="text-sm text-gray-600 dark:text-ghost-400">
|
<p class="text-sm text-gray-600 dark:text-guild-400">
|
||||||
Next Billing Date
|
Next Billing Date
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-900 dark:text-ghost-100">
|
<p class="text-gray-900 dark:text-guild-100">
|
||||||
{{ formatDate(memberData.nextBillingDate) }}
|
{{ formatDate(memberData.nextBillingDate) }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -669,15 +669,15 @@
|
||||||
<!-- Change Contribution Level -->
|
<!-- Change Contribution Level -->
|
||||||
<div>
|
<div>
|
||||||
<h2
|
<h2
|
||||||
class="text-2xl font-semibold mb-6 text-gray-900 dark:text-ghost-100 ethereal-text"
|
class="text-2xl font-semibold mb-6 text-gray-900 dark:text-guild-100 warm-text"
|
||||||
>
|
>
|
||||||
Change Contribution Level
|
Change Contribution Level
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="backdrop-blur-sm bg-white/80 dark:bg-ghost-800/50 border border-gray-200 dark:border-ghost-700 rounded-lg p-6"
|
class="backdrop-blur-sm bg-white/80 dark:bg-guild-800/50 border border-gray-200 dark:border-guild-700 rounded-lg p-6"
|
||||||
>
|
>
|
||||||
<p class="text-gray-700 dark:text-ghost-300 mb-6">
|
<p class="text-gray-700 dark:text-guild-300 mb-6">
|
||||||
Choose a new contribution level that works for you.
|
Choose a new contribution level that works for you.
|
||||||
Changes will take effect on your next billing cycle.
|
Changes will take effect on your next billing cycle.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -691,14 +691,14 @@
|
||||||
'w-full text-left p-4 rounded-lg border-2 transition-all',
|
'w-full text-left p-4 rounded-lg border-2 transition-all',
|
||||||
selectedContributionTier === tier.value
|
selectedContributionTier === tier.value
|
||||||
? 'border-blue-400 bg-blue-500/20'
|
? 'border-blue-400 bg-blue-500/20'
|
||||||
: 'border-gray-300 dark:border-ghost-600 bg-gray-50 dark:bg-ghost-900/30 hover:border-blue-300 dark:hover:border-ghost-500',
|
: 'border-gray-300 dark:border-guild-600 bg-gray-50 dark:bg-guild-900/30 hover:border-blue-300 dark:hover:border-guild-500',
|
||||||
]"
|
]"
|
||||||
@click="selectedContributionTier = tier.value"
|
@click="selectedContributionTier = tier.value"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p
|
<p
|
||||||
class="font-medium text-gray-900 dark:text-ghost-100"
|
class="font-medium text-gray-900 dark:text-guild-100"
|
||||||
>
|
>
|
||||||
{{ tier.label }}
|
{{ tier.label }}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -745,20 +745,20 @@
|
||||||
<!-- Cancel Membership -->
|
<!-- Cancel Membership -->
|
||||||
<div>
|
<div>
|
||||||
<h2
|
<h2
|
||||||
class="text-2xl font-semibold mb-6 text-gray-900 dark:text-ghost-100 ethereal-text"
|
class="text-2xl font-semibold mb-6 text-gray-900 dark:text-guild-100 warm-text"
|
||||||
>
|
>
|
||||||
Cancel Membership
|
Cancel Membership
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="backdrop-blur-sm bg-white/80 dark:bg-ghost-800/50 border border-gray-200 dark:border-ghost-700 rounded-lg p-6"
|
class="backdrop-blur-sm bg-white/80 dark:bg-guild-800/50 border border-gray-200 dark:border-guild-700 rounded-lg p-6"
|
||||||
>
|
>
|
||||||
<p class="text-gray-700 dark:text-ghost-300 mb-4">
|
<p class="text-gray-700 dark:text-guild-300 mb-4">
|
||||||
We're sorry to see you go. If you cancel, you'll lose
|
We're sorry to see you go. If you cancel, you'll lose
|
||||||
access to member benefits at the end of your current
|
access to member benefits at the end of your current
|
||||||
billing period.
|
billing period.
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-gray-600 dark:text-ghost-400 mb-6">
|
<p class="text-sm text-gray-600 dark:text-guild-400 mb-6">
|
||||||
Need a break? Consider switching to the free tier instead.
|
Need a break? Consider switching to the free tier instead.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
<!-- Skills Filter -->
|
<!-- Skills Filter -->
|
||||||
<div v-if="availableSkills && availableSkills.length > 0">
|
<div v-if="availableSkills && availableSkills.length > 0">
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<span class="text-sm text-ghost-400 mr-2 self-center"
|
<span class="text-sm text-guild-400 mr-2 self-center"
|
||||||
>Filter by skill:</span
|
>Filter by skill:</span
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
|
|
@ -57,7 +57,7 @@
|
||||||
:class="
|
:class="
|
||||||
selectedSkills.includes(skill)
|
selectedSkills.includes(skill)
|
||||||
? 'bg-purple-100 dark:bg-purple-500/20 text-purple-700 dark:text-purple-300 border-purple-300 dark:border-purple-500/50'
|
? 'bg-purple-100 dark:bg-purple-500/20 text-purple-700 dark:text-purple-300 border-purple-300 dark:border-purple-500/50'
|
||||||
: 'bg-gray-100 dark:bg-ghost-800/50 text-gray-700 dark:text-ghost-400 border-gray-300 dark:border-ghost-700 hover:border-gray-400 dark:hover:border-ghost-600'
|
: 'bg-gray-100 dark:bg-guild-800/50 text-gray-700 dark:text-guild-400 border-gray-300 dark:border-guild-700 hover:border-gray-400 dark:hover:border-guild-600'
|
||||||
"
|
"
|
||||||
@click="toggleSkill(skill)"
|
@click="toggleSkill(skill)"
|
||||||
>
|
>
|
||||||
|
|
@ -81,7 +81,7 @@
|
||||||
<!-- Peer Support Topics Filter -->
|
<!-- Peer Support Topics Filter -->
|
||||||
<div v-if="availableTopics && availableTopics.length > 0">
|
<div v-if="availableTopics && availableTopics.length > 0">
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<span class="text-sm text-ghost-400 mr-2 self-center"
|
<span class="text-sm text-guild-400 mr-2 self-center"
|
||||||
>Filter by peer support topic:</span
|
>Filter by peer support topic:</span
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
|
|
@ -95,7 +95,7 @@
|
||||||
:class="
|
:class="
|
||||||
selectedTopics.includes(topic)
|
selectedTopics.includes(topic)
|
||||||
? 'bg-purple-100 dark:bg-purple-500/20 text-purple-700 dark:text-purple-300 border-purple-300 dark:border-purple-500/50'
|
? 'bg-purple-100 dark:bg-purple-500/20 text-purple-700 dark:text-purple-300 border-purple-300 dark:border-purple-500/50'
|
||||||
: 'bg-gray-100 dark:bg-ghost-800/50 text-gray-700 dark:text-ghost-400 border-gray-300 dark:border-ghost-700 hover:border-gray-400 dark:hover:border-ghost-600'
|
: 'bg-gray-100 dark:bg-guild-800/50 text-gray-700 dark:text-guild-400 border-gray-300 dark:border-guild-700 hover:border-gray-400 dark:hover:border-guild-600'
|
||||||
"
|
"
|
||||||
@click="toggleTopic(topic)"
|
@click="toggleTopic(topic)"
|
||||||
>
|
>
|
||||||
|
|
@ -126,7 +126,7 @@
|
||||||
"
|
"
|
||||||
class="flex items-center gap-2 text-sm flex-wrap"
|
class="flex items-center gap-2 text-sm flex-wrap"
|
||||||
>
|
>
|
||||||
<span class="text-ghost-400">Active filters:</span>
|
<span class="text-guild-400">Active filters:</span>
|
||||||
<span
|
<span
|
||||||
v-if="selectedCircle && selectedCircle !== 'all'"
|
v-if="selectedCircle && selectedCircle !== 'all'"
|
||||||
class="px-2 py-1 bg-purple-100 dark:bg-purple-500/20 text-purple-700 dark:text-purple-300 rounded-full border border-purple-300 dark:border-purple-500/30 flex items-center gap-1"
|
class="px-2 py-1 bg-purple-100 dark:bg-purple-500/20 text-purple-700 dark:text-purple-300 rounded-full border border-purple-300 dark:border-purple-500/30 flex items-center gap-1"
|
||||||
|
|
@ -173,13 +173,13 @@
|
||||||
<div
|
<div
|
||||||
class="w-8 h-8 border-4 border-purple-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"
|
class="w-8 h-8 border-4 border-purple-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"
|
||||||
></div>
|
></div>
|
||||||
<p class="text-ghost-400">Loading members...</p>
|
<p class="text-guild-400">Loading members...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Members List -->
|
<!-- Members List -->
|
||||||
<div v-else-if="members.length > 0">
|
<div v-else-if="members.length > 0">
|
||||||
<div class="mb-4 text-ghost-400 text-sm">
|
<div class="mb-4 text-guild-400 text-sm">
|
||||||
{{ totalCount }} {{ totalCount === 1 ? "member" : "members" }} found
|
{{ totalCount }} {{ totalCount === 1 ? "member" : "members" }} found
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -187,7 +187,7 @@
|
||||||
<div
|
<div
|
||||||
v-for="member in members"
|
v-for="member in members"
|
||||||
:key="member._id"
|
:key="member._id"
|
||||||
class="relative backdrop-blur-sm bg-ghost-900/50 border border-ghost-700/50 rounded-lg p-6 hover:border-purple-500/50 transition-all group"
|
class="relative backdrop-blur-sm bg-guild-900/50 border border-guild-700/50 rounded-lg p-6 hover:border-purple-500/50 transition-all group"
|
||||||
>
|
>
|
||||||
<!-- Peer Support Sticker Badge -->
|
<!-- Peer Support Sticker Badge -->
|
||||||
<PeerSupportBadge
|
<PeerSupportBadge
|
||||||
|
|
@ -199,7 +199,7 @@
|
||||||
<div class="flex items-start gap-4 mb-4">
|
<div class="flex items-start gap-4 mb-4">
|
||||||
<!-- Avatar -->
|
<!-- Avatar -->
|
||||||
<div
|
<div
|
||||||
class="w-16 h-16 rounded-lg bg-ghost-800 border border-ghost-700 flex items-center justify-center flex-shrink-0 group-hover:border-purple-500/50 transition-colors"
|
class="w-16 h-16 rounded-lg bg-guild-800 border border-guild-700 flex items-center justify-center flex-shrink-0 group-hover:border-purple-500/50 transition-colors"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
v-if="member.avatar"
|
v-if="member.avatar"
|
||||||
|
|
@ -207,16 +207,16 @@
|
||||||
:alt="member.name"
|
:alt="member.name"
|
||||||
class="w-12 h-12 object-contain"
|
class="w-12 h-12 object-contain"
|
||||||
/>
|
/>
|
||||||
<span v-else class="text-2xl text-ghost-600">👻</span>
|
<span v-else class="text-2xl text-guild-600">👻</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Name and Meta Info -->
|
<!-- Name and Meta Info -->
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<div class="flex items-baseline gap-2 flex-wrap mb-2">
|
<div class="flex items-baseline gap-2 flex-wrap mb-2">
|
||||||
<h3 class="font-semibold text-lg text-ghost-100">
|
<h3 class="font-semibold text-lg text-guild-100">
|
||||||
{{ member.name }}
|
{{ member.name }}
|
||||||
</h3>
|
</h3>
|
||||||
<span v-if="member.pronouns" class="text-sm text-ghost-400">
|
<span v-if="member.pronouns" class="text-sm text-guild-400">
|
||||||
{{ member.pronouns }}
|
{{ member.pronouns }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -227,13 +227,13 @@
|
||||||
>
|
>
|
||||||
{{ circleLabels[member.circle] }}
|
{{ circleLabels[member.circle] }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="member.studio" class="text-sm text-ghost-400">
|
<span v-if="member.studio" class="text-sm text-guild-400">
|
||||||
{{ member.studio }}
|
{{ member.studio }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="member.location" class="text-sm text-ghost-500">
|
<span v-if="member.location" class="text-sm text-guild-500">
|
||||||
📍 {{ member.location }}
|
📍 {{ member.location }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="member.timeZone" class="text-sm text-ghost-500">
|
<span v-if="member.timeZone" class="text-sm text-guild-500">
|
||||||
🕐 {{ member.timeZone }}
|
🕐 {{ member.timeZone }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -243,7 +243,7 @@
|
||||||
<!-- Bio -->
|
<!-- Bio -->
|
||||||
<div
|
<div
|
||||||
v-if="member.bio"
|
v-if="member.bio"
|
||||||
class="mb-4 text-ghost-300 text-sm leading-relaxed prose prose-invert prose-sm max-w-none"
|
class="mb-4 text-guild-300 text-sm leading-relaxed prose prose-invert prose-sm max-w-none"
|
||||||
v-html="renderMarkdown(member.bio)"
|
v-html="renderMarkdown(member.bio)"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
|
|
@ -280,7 +280,7 @@
|
||||||
<!-- Personal Message -->
|
<!-- Personal Message -->
|
||||||
<div
|
<div
|
||||||
v-if="member.peerSupport.personalMessage"
|
v-if="member.peerSupport.personalMessage"
|
||||||
class="text-sm text-ghost-300 italic mb-2"
|
class="text-sm text-guild-300 italic mb-2"
|
||||||
>
|
>
|
||||||
"{{ member.peerSupport.personalMessage }}"
|
"{{ member.peerSupport.personalMessage }}"
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -288,7 +288,7 @@
|
||||||
<!-- Availability -->
|
<!-- Availability -->
|
||||||
<div
|
<div
|
||||||
v-if="member.peerSupport.availability"
|
v-if="member.peerSupport.availability"
|
||||||
class="text-xs text-ghost-400 mb-2"
|
class="text-xs text-guild-400 mb-2"
|
||||||
>
|
>
|
||||||
Availability: {{ member.peerSupport.availability }}
|
Availability: {{ member.peerSupport.availability }}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -326,7 +326,7 @@
|
||||||
</h5>
|
</h5>
|
||||||
<p
|
<p
|
||||||
v-if="member.offering.description"
|
v-if="member.offering.description"
|
||||||
class="text-ghost-300 text-sm"
|
class="text-guild-300 text-sm"
|
||||||
>
|
>
|
||||||
{{ member.offering.description }}
|
{{ member.offering.description }}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -353,7 +353,7 @@
|
||||||
</h5>
|
</h5>
|
||||||
<p
|
<p
|
||||||
v-if="member.lookingFor.description"
|
v-if="member.lookingFor.description"
|
||||||
class="text-ghost-300 text-sm"
|
class="text-guild-300 text-sm"
|
||||||
>
|
>
|
||||||
{{ member.lookingFor.description }}
|
{{ member.lookingFor.description }}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -386,7 +386,7 @@
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
class="text-ghost-600"
|
class="text-guild-600"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
|
|
@ -396,10 +396,10 @@
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-medium text-ghost-300 mb-2">
|
<h3 class="text-lg font-medium text-guild-300 mb-2">
|
||||||
No members found
|
No members found
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-ghost-400 mb-6">
|
<p class="text-guild-400 mb-6">
|
||||||
Try adjusting your search or filters
|
Try adjusting your search or filters
|
||||||
</p>
|
</p>
|
||||||
<UButton variant="outline" @click="clearAllFilters">
|
<UButton variant="outline" @click="clearAllFilters">
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
size="large"
|
size="large"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<section class="py-16 bg-ghost-900">
|
<section class="py-16 bg-guild-900">
|
||||||
<UContainer>
|
<UContainer>
|
||||||
<div class="max-w-4xl mx-auto">
|
<div class="max-w-4xl mx-auto">
|
||||||
<!-- Welcome Message -->
|
<!-- Welcome Message -->
|
||||||
|
|
@ -26,17 +26,17 @@
|
||||||
class="w-full h-full object-contain"
|
class="w-full h-full object-contain"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="text-2xl font-bold text-ghost-100 mb-4">
|
<h2 class="text-2xl font-bold text-guild-100 mb-4">
|
||||||
Hey {{ memberData?.name || "there" }}!
|
Hey {{ memberData?.name || "there" }}!
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-lg text-ghost-300 max-w-2xl mx-auto">
|
<p class="text-lg text-guild-300 max-w-2xl mx-auto">
|
||||||
You've joined a an awesome community!!👻 Welcome to Ghost guild…
|
You've joined a an awesome community!!👻 Welcome to Ghost guild…
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Getting Started Steps -->
|
<!-- Getting Started Steps -->
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 mb-16">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 mb-16">
|
||||||
<div class="p-6 bg-ghost-800/50 rounded-xl border border-ghost-700">
|
<div class="p-6 bg-guild-800/50 rounded-xl border border-guild-700">
|
||||||
<div
|
<div
|
||||||
class="w-12 h-12 bg-purple-500/20 rounded-lg flex items-center justify-center mb-4"
|
class="w-12 h-12 bg-purple-500/20 rounded-lg flex items-center justify-center mb-4"
|
||||||
>
|
>
|
||||||
|
|
@ -45,10 +45,10 @@
|
||||||
class="w-6 h-6 text-purple-400"
|
class="w-6 h-6 text-purple-400"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="font-semibold text-ghost-100 mb-2">
|
<h3 class="font-semibold text-guild-100 mb-2">
|
||||||
1. Complete Your Profile
|
1. Complete Your Profile
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-ghost-400 mb-4">
|
<p class="text-sm text-guild-400 mb-4">
|
||||||
Tell the community about yourself, your skills, and what you're
|
Tell the community about yourself, your skills, and what you're
|
||||||
looking for.
|
looking for.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -57,7 +57,7 @@
|
||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="p-6 bg-ghost-800/50 rounded-xl border border-ghost-700">
|
<div class="p-6 bg-guild-800/50 rounded-xl border border-guild-700">
|
||||||
<div
|
<div
|
||||||
class="w-12 h-12 bg-blue-500/20 rounded-lg flex items-center justify-center mb-4"
|
class="w-12 h-12 bg-blue-500/20 rounded-lg flex items-center justify-center mb-4"
|
||||||
>
|
>
|
||||||
|
|
@ -66,10 +66,10 @@
|
||||||
class="w-6 h-6 text-blue-400"
|
class="w-6 h-6 text-blue-400"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="font-semibold text-ghost-100 mb-2">
|
<h3 class="font-semibold text-guild-100 mb-2">
|
||||||
2. Join an Event
|
2. Join an Event
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-ghost-400 mb-4">
|
<p class="text-sm text-guild-400 mb-4">
|
||||||
From workshops to game nights, events are the heart of our
|
From workshops to game nights, events are the heart of our
|
||||||
community.
|
community.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -78,16 +78,16 @@
|
||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="p-6 bg-ghost-800/50 rounded-xl border border-ghost-700">
|
<div class="p-6 bg-guild-800/50 rounded-xl border border-guild-700">
|
||||||
<div
|
<div
|
||||||
class="w-12 h-12 bg-green-500/20 rounded-lg flex items-center justify-center mb-4"
|
class="w-12 h-12 bg-green-500/20 rounded-lg flex items-center justify-center mb-4"
|
||||||
>
|
>
|
||||||
<Icon name="heroicons:users" class="w-6 h-6 text-green-400" />
|
<Icon name="heroicons:users" class="w-6 h-6 text-green-400" />
|
||||||
</div>
|
</div>
|
||||||
<h3 class="font-semibold text-ghost-100 mb-2">
|
<h3 class="font-semibold text-guild-100 mb-2">
|
||||||
3. Meet the Community
|
3. Meet the Community
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-ghost-400 mb-4">
|
<p class="text-sm text-guild-400 mb-4">
|
||||||
Connect with other members and find peers for support and
|
Connect with other members and find peers for support and
|
||||||
collaboration.
|
collaboration.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -99,12 +99,12 @@
|
||||||
|
|
||||||
<!-- About Circles -->
|
<!-- About Circles -->
|
||||||
<div
|
<div
|
||||||
class="p-8 bg-ghost-800/30 rounded-2xl border border-ghost-700 mb-16"
|
class="p-8 bg-guild-800/30 rounded-2xl border border-guild-700 mb-16"
|
||||||
>
|
>
|
||||||
<h3 class="text-xl font-bold text-ghost-100 mb-4">
|
<h3 class="text-xl font-bold text-guild-100 mb-4">
|
||||||
Understanding Circles
|
Understanding Circles
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-ghost-300 mb-6">
|
<p class="text-guild-300 mb-6">
|
||||||
Ghost Guild is organized into three circles based on where you are
|
Ghost Guild is organized into three circles based on where you are
|
||||||
in your journey. Your circle helps us tailor events and resources
|
in your journey. Your circle helps us tailor events and resources
|
||||||
to your needs.
|
to your needs.
|
||||||
|
|
@ -116,10 +116,10 @@
|
||||||
:class="
|
:class="
|
||||||
memberData?.circle === 'community'
|
memberData?.circle === 'community'
|
||||||
? 'bg-purple-500/20 border border-purple-500/50'
|
? 'bg-purple-500/20 border border-purple-500/50'
|
||||||
: 'bg-ghost-800/50'
|
: 'bg-guild-800/50'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<h4 class="font-semibold text-ghost-100 mb-2">
|
<h4 class="font-semibold text-guild-100 mb-2">
|
||||||
Community Circle
|
Community Circle
|
||||||
<span
|
<span
|
||||||
v-if="memberData?.circle === 'community'"
|
v-if="memberData?.circle === 'community'"
|
||||||
|
|
@ -127,7 +127,7 @@
|
||||||
>← You're here</span
|
>← You're here</span
|
||||||
>
|
>
|
||||||
</h4>
|
</h4>
|
||||||
<p class="text-sm text-ghost-400">
|
<p class="text-sm text-guild-400">
|
||||||
For those exploring solidarity economics and alternative
|
For those exploring solidarity economics and alternative
|
||||||
studio models.
|
studio models.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -138,10 +138,10 @@
|
||||||
:class="
|
:class="
|
||||||
memberData?.circle === 'founder'
|
memberData?.circle === 'founder'
|
||||||
? 'bg-purple-500/20 border border-purple-500/50'
|
? 'bg-purple-500/20 border border-purple-500/50'
|
||||||
: 'bg-ghost-800/50'
|
: 'bg-guild-800/50'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<h4 class="font-semibold text-ghost-100 mb-2">
|
<h4 class="font-semibold text-guild-100 mb-2">
|
||||||
Founder Circle
|
Founder Circle
|
||||||
<span
|
<span
|
||||||
v-if="memberData?.circle === 'founder'"
|
v-if="memberData?.circle === 'founder'"
|
||||||
|
|
@ -149,7 +149,7 @@
|
||||||
>← You're here</span
|
>← You're here</span
|
||||||
>
|
>
|
||||||
</h4>
|
</h4>
|
||||||
<p class="text-sm text-ghost-400">
|
<p class="text-sm text-guild-400">
|
||||||
For those actively building or running a cooperative or
|
For those actively building or running a cooperative or
|
||||||
solidarity-based studio.
|
solidarity-based studio.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -160,10 +160,10 @@
|
||||||
:class="
|
:class="
|
||||||
memberData?.circle === 'practitioner'
|
memberData?.circle === 'practitioner'
|
||||||
? 'bg-purple-500/20 border border-purple-500/50'
|
? 'bg-purple-500/20 border border-purple-500/50'
|
||||||
: 'bg-ghost-800/50'
|
: 'bg-guild-800/50'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<h4 class="font-semibold text-ghost-100 mb-2">
|
<h4 class="font-semibold text-guild-100 mb-2">
|
||||||
Practitioner Circle
|
Practitioner Circle
|
||||||
<span
|
<span
|
||||||
v-if="memberData?.circle === 'practitioner'"
|
v-if="memberData?.circle === 'practitioner'"
|
||||||
|
|
@ -171,7 +171,7 @@
|
||||||
>← You're here</span
|
>← You're here</span
|
||||||
>
|
>
|
||||||
</h4>
|
</h4>
|
||||||
<p class="text-sm text-ghost-400">
|
<p class="text-sm text-guild-400">
|
||||||
For consultants, advisors, and professionals supporting
|
For consultants, advisors, and professionals supporting
|
||||||
cooperative game studios.
|
cooperative game studios.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -181,9 +181,9 @@
|
||||||
|
|
||||||
<!-- Resources -->
|
<!-- Resources -->
|
||||||
<div
|
<div
|
||||||
class="p-8 bg-ghost-800/30 rounded-2xl border border-ghost-700 mb-16"
|
class="p-8 bg-guild-800/30 rounded-2xl border border-guild-700 mb-16"
|
||||||
>
|
>
|
||||||
<h3 class="text-xl font-bold text-ghost-100 mb-4">
|
<h3 class="text-xl font-bold text-guild-100 mb-4">
|
||||||
Resources & Support
|
Resources & Support
|
||||||
</h3>
|
</h3>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
|
@ -197,10 +197,10 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4 class="font-semibold text-ghost-100 mb-1">
|
<h4 class="font-semibold text-guild-100 mb-1">
|
||||||
Resource Library
|
Resource Library
|
||||||
</h4>
|
</h4>
|
||||||
<p class="text-sm text-ghost-400 mb-2">
|
<p class="text-sm text-guild-400 mb-2">
|
||||||
Templates, guides, and tools for building solidarity-based
|
Templates, guides, and tools for building solidarity-based
|
||||||
studios.
|
studios.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -226,10 +226,10 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4 class="font-semibold text-ghost-100 mb-1">
|
<h4 class="font-semibold text-guild-100 mb-1">
|
||||||
Peer Support
|
Peer Support
|
||||||
</h4>
|
</h4>
|
||||||
<p class="text-sm text-ghost-400 mb-2">
|
<p class="text-sm text-guild-400 mb-2">
|
||||||
Connect 1:1 with community members for advice and support.
|
Connect 1:1 with community members for advice and support.
|
||||||
</p>
|
</p>
|
||||||
<UButton
|
<UButton
|
||||||
|
|
|
||||||
0
public/fonts/.gitkeep
Normal file
0
public/fonts/.gitkeep
Normal file
0
public/textures/.gitkeep
Normal file
0
public/textures/.gitkeep
Normal file
Loading…
Add table
Add a link
Reference in a new issue