diff --git a/.env.example b/.env.example index 8a6cadf..d59ecce 100644 --- a/.env.example +++ b/.env.example @@ -6,8 +6,6 @@ MONGODB_URI=mongodb://localhost:27017/ghostguild # HELCIM_API_TOKEN=your-live-helcim-api-token HELCIM_API_TOKEN=your-test-helcim-api-token NUXT_PUBLIC_HELCIM_ACCOUNT_ID=your-helcim-account-id -NUXT_HELCIM_MONTHLY_PLAN_ID= -NUXT_HELCIM_ANNUAL_PLAN_ID= # Email Configuration (Resend) RESEND_API_KEY=your-resend-api-key @@ -16,8 +14,6 @@ RESEND_FROM_EMAIL=noreply@ghostguild.org # Slack Integration SLACK_WEBHOOK_URL=your-slack-webhook-url SLACK_OAUTH_TOKEN=your-slack-oauth-token -# AdminGhost bot token — used for admin-only channel creation. Falls back to SLACK_BOT_TOKEN if unset. -SLACK_ADMIN_BOT_TOKEN=xoxb-adminghost-token # JWT Secret for authentication JWT_SECRET=your-jwt-secret-key-change-this-in-production @@ -31,7 +27,4 @@ BASE_URL=http://localhost:3000 # OIDC Provider (for Outline Wiki SSO) OIDC_CLIENT_ID=outline-wiki OIDC_CLIENT_SECRET= -OIDC_COOKIE_SECRET= - -# Outline Wiki Integration -OUTLINE_API_KEY= \ No newline at end of file +OIDC_COOKIE_SECRET= \ No newline at end of file diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml deleted file mode 100644 index 2a29c40..0000000 --- a/.forgejo/workflows/test.yml +++ /dev/null @@ -1,90 +0,0 @@ -name: Test - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - vitest: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: npm - - run: npm ci - - run: npm run test:run - - playwright: - runs-on: ubuntu-latest - needs: vitest - env: - MONGODB_URI: mongodb://mongo-ci:27017/ghostguild-test - JWT_SECRET: ci-test-jwt-secret - RESEND_API_KEY: re_ci_dummy_not_used - HELCIM_API_TOKEN: helcim_ci_dummy_not_used - OIDC_COOKIE_SECRET: ci-oidc-cookie-secret-not-secret - NUXT_PUBLIC_COMING_SOON: 'false' - NODE_ENV: development - ALLOW_DEV_TEST_ENDPOINTS: 'true' - BASE_URL: http://localhost:3000 - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: npm - - run: npm ci - - run: npx playwright install --with-deps chromium - - name: Start MongoDB - run: | - docker rm -f mongo-ci 2>/dev/null || true - docker run -d --name mongo-ci mongo:7 - # Forgejo runs each job inside its own container; attach Mongo to - # that container's network so MONGODB_URI=mongodb://mongo-ci:27017 - # resolves from inside the runner. - RUNNER_NET=$(docker inspect "$HOSTNAME" --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}' | awk '{print $1}') - docker network connect "$RUNNER_NET" mongo-ci - docker ps - - name: Wait for MongoDB - run: timeout 30 sh -c 'until docker exec mongo-ci mongosh --quiet --eval "1" >/dev/null 2>&1; do sleep 1; done' - - name: MongoDB log on failure - if: failure() - run: docker logs mongo-ci || true - - name: Seed test data - run: node scripts/seed-all.js && node scripts/seed-tags.js - - run: npm run build - - name: Start server - run: node .output/server/index.mjs > /tmp/server.log 2>&1 & - env: - PORT: 3000 - - name: Wait for server - run: timeout 30 sh -c 'until curl -sf http://localhost:3000; do sleep 1; done' - - name: Server log on failure - if: failure() - run: cat /tmp/server.log || true - - run: npx playwright test - - uses: actions/upload-artifact@v3 - if: failure() - with: - name: playwright-report - path: | - playwright-report/ - e2e/test-results/ - retention-days: 7 - - notify: - name: Notify on failure - runs-on: ubuntu-latest - needs: [vitest, playwright] - if: failure() - steps: - - name: Post to Slack - run: | - curl -s -X POST "${{ secrets.SLACK_WEBHOOK_URL }}" \ - -H 'Content-type: application/json' \ - --data "{\"text\":\":x: *Ghost Guild CI failed* on \`${{ github.ref_name }}\`\nCommit: ${{ github.sha }}\n${{ github.server_url }}/${{ github.repository }}/actions\"}" - diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..c041dbb --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,94 @@ +name: Test + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + vitest: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npm run test:run + + playwright: + runs-on: ubuntu-latest + needs: vitest + services: + mongo: + image: mongo:7 + ports: + - 27017:27017 + env: + MONGODB_URI: mongodb://localhost:27017/ghostguild-test + JWT_SECRET: ci-test-jwt-secret + NUXT_PUBLIC_COMING_SOON: 'false' + NODE_ENV: development + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npx playwright install --with-deps chromium + - run: npm run build + - name: Start server + run: node .output/server/index.mjs & + env: + PORT: 3000 + - name: Wait for server + run: npx wait-on http://localhost:3000 --timeout 30000 + - run: npx playwright test --ignore-snapshots + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: playwright-report + path: | + playwright-report/ + e2e/test-results/ + retention-days: 7 + + visual: + runs-on: ubuntu-latest + needs: vitest + continue-on-error: true + services: + mongo: + image: mongo:7 + ports: + - 27017:27017 + env: + MONGODB_URI: mongodb://localhost:27017/ghostguild-test + JWT_SECRET: ci-test-jwt-secret + NUXT_PUBLIC_COMING_SOON: 'false' + NODE_ENV: development + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npx playwright install --with-deps chromium + - run: npm run build + - name: Start server + run: node .output/server/index.mjs & + env: + PORT: 3000 + - name: Wait for server + run: npx wait-on http://localhost:3000 --timeout 30000 + - run: npx playwright test e2e/visual/ + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: visual-diffs + path: e2e/test-results/ + retention-days: 7 diff --git a/.gitignore b/.gitignore index 3907ee0..15f2a76 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,7 @@ logs .fleet .idea /docs/ -/*.md +*.md/ # Local env files .env @@ -26,18 +26,6 @@ logs !.env.example scripts/*.js -# Migration backup files -.migration-backup-*.json - # Playwright e2e/test-results/ playwright-report/ -e2e/.auth/ - -# Worktrees -.worktrees/ -.claude/worktrees/ -.superpowers/ - -.claude -scripts/dump-babyghosts-preregistrations.mjs diff --git a/.husky/pre-push b/.husky/pre-push old mode 100755 new mode 100644 diff --git a/.serena/project.yml b/.serena/project.yml index 0d43951..9d24cb3 100644 --- a/.serena/project.yml +++ b/.serena/project.yml @@ -3,26 +3,21 @@ project_name: "ghostguild-org" # list of languages for which language servers are started; choose from: -# al angular ansible bash clojure -# cpp cpp_ccls crystal csharp csharp_omnisharp -# dart elixir elm erlang fortran -# fsharp go groovy haskell haxe -# hlsl html java json julia -# kotlin lean4 lua luau markdown -# matlab msl nix ocaml pascal -# perl php php_phpactor powershell python -# python_jedi python_ty r rego ruby -# ruby_solargraph rust scala scss solidity -# swift systemverilog terraform toml typescript -# typescript_vts vue yaml zig +# al bash clojure cpp csharp +# csharp_omnisharp dart elixir elm erlang +# fortran fsharp go groovy haskell +# java julia kotlin lua markdown +# matlab nix pascal perl php +# php_phpactor powershell python python_jedi r +# rego ruby ruby_solargraph rust scala +# swift terraform toml typescript typescript_vts +# vue yaml zig # (This list may be outdated. For the current list, see values of Language enum here: # https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py # For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.) # Note: # - For C, use cpp # - For JavaScript, use typescript -# - For Angular projects, use angular (subsumes typescript+html; requires `npm install` in the project root) -# - For SCSS / Sass / plain CSS, use scss (some-sass-language-server handles all three) # - For Free Pascal/Lazarus, use pascal # Special requirements: # Some languages require additional setup/installations. @@ -70,17 +65,53 @@ read_only: false # list of tool names to exclude. # This extends the existing exclusions (e.g. from the global configuration) -# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html +# +# Below is the complete list of tools for convenience. +# To make sure you have the latest list of tools, and to view their descriptions, +# execute `uv run scripts/print_tool_overview.py`. +# +# * `activate_project`: Activates a project by name. +# * `check_onboarding_performed`: Checks whether project onboarding was already performed. +# * `create_text_file`: Creates/overwrites a file in the project directory. +# * `delete_lines`: Deletes a range of lines within a file. +# * `delete_memory`: Deletes a memory from Serena's project-specific memory store. +# * `execute_shell_command`: Executes a shell command. +# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. +# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). +# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). +# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. +# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. +# * `initial_instructions`: Gets the initial instructions for the current project. +# Should only be used in settings where the system prompt cannot be set, +# e.g. in clients you have no control over, like Claude Desktop. +# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. +# * `insert_at_line`: Inserts content at a given line in a file. +# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. +# * `list_dir`: Lists files and directories in the given directory (optionally with recursion). +# * `list_memories`: Lists memories in Serena's project-specific memory store. +# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). +# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). +# * `read_file`: Reads a file within the project directory. +# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. +# * `remove_project`: Removes a project from the Serena configuration. +# * `replace_lines`: Replaces a range of lines within a file with new content. +# * `replace_symbol_body`: Replaces the full definition of a symbol. +# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. +# * `search_for_pattern`: Performs a search for a pattern in the project. +# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. +# * `switch_modes`: Activates modes by providing a list of their names +# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. +# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. +# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. +# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. excluded_tools: [] # list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default). # This extends the existing inclusions (e.g. from the global configuration). -# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html included_optional_tools: [] # fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools. # This cannot be combined with non-empty excluded_tools or included_optional_tools. -# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html fixed_tools: [] # list of mode names to that are always to be included in the set of active modes @@ -91,14 +122,11 @@ fixed_tools: [] # Set this to a list of mode names to always include the respective modes for this project. base_modes: -# list of mode names that are to be activated by default, overriding the setting in the global configuration. -# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes. -# If the setting is undefined/empty, the default_modes from the global configuration (serena_config.yml) apply. +# list of mode names that are to be activated by default. +# The full set of modes to be activated is base_modes + default_modes. +# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply. # Otherwise, this overrides the setting from the global configuration (serena_config.yml). -# Therefore, you can set this to [] if you do not want the default modes defined in the global config to apply -# for this project. # This setting can, in turn, be overridden by CLI parameters (--mode). -# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes default_modes: # initial prompt for the project. It will always be given to the LLM upon activating the project @@ -122,19 +150,3 @@ read_only_memory_patterns: [] # Extends the list from the global configuration, merging the two lists. # Example: ["_archive/.*", "_episodes/.*"] ignored_memory_patterns: [] - -# list of mode names to be activated additionally for this project, e.g. ["query-projects"] -# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes. -# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes -added_modes: - -# list of additional workspace folder paths for cross-package reference support (e.g. in monorepos). -# Paths can be absolute or relative to the project root. -# Each folder is registered as an LSP workspace folder, enabling language servers to discover -# symbols and references across package boundaries. -# Currently supported for: TypeScript. -# Example: -# additional_workspace_folders: -# - ../sibling-package -# - ../shared-lib -additional_workspace_folders: [] diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..3b1685d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,99 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +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. + +## Commands + +```bash +npm run dev # Start dev server at http://localhost:3000 +npm run build # Production build +npm run preview # Preview production build +npm run test:run # Vitest single run (pre-push hook) +npm run test:e2e # Playwright E2E (needs dev server + MongoDB) +npm run test:a11y # Accessibility scans +npm run test:all # Vitest + Playwright +``` + +**Dev helpers:** `GET /api/dev/test-login` — creates a test admin user and sets auth cookie (dev only, blocked in production). Navigate to this URL to access admin pages during development. + +**Testing:** Vitest for unit/handler tests (`tests/`), Playwright for E2E (`e2e/`). Husky pre-push hook runs Vitest. See `TESTING.md` for details. + +## Architecture + +### Stack + +- **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`) + +### Key Directories + +- `app/composables/` — State management via `useState()` (no Pinia/Vuex). Key composables: `useAuth`, `useHelcim`, `useMemberPayment`, `useMemberStatus` +- `app/config/` — Circle definitions (`circles.js`) and contribution tiers (`contributions.js`) used across frontend and forms +- `app/middleware/` — Route guards: `auth.js` (member pages), `admin.js` (admin pages), `coming-soon.global.js` (launch gate) +- `app/layouts/` — `default` (sidebar, member/public), `admin` (sidebar, admin pages), `landing`, `coming-soon` +- `server/api/` — Nitro API routes organized by feature: `auth/`, `events/`, `members/`, `helcim/`, `series/`, `updates/`, `admin/`, `slack/`, `dev/` (dev-only helpers) +- `server/models/` — Mongoose schemas: `Member`, `Event`, `Series`, `Update` +- `server/utils/` — Service integrations: `mongoose.js`, `helcim.js`, `resend.js`, `slack.ts`, `tickets.js` + +### Domain Model + +Three membership **circles**: Community, Founder, Practitioner — each with different access and context. Five **contribution tiers**: $0, $5, $15, $30, $50/month via Helcim subscriptions. + +Member statuses: `pending_payment`, `active`, `suspended`, `cancelled`. + +Events support ticketing with circle-specific pricing overrides and can be grouped into Series with bundled passes. + +### Design System (Zine Direction) + +- **Palette:** CSS custom properties in `:root` / `.dark` blocks in `app/assets/css/main.css` — `--bg` (cream/#f4efe4), `--surface`, `--border`, `--candle` (gold accent), `--ember` (rust accent), `--text`, `--text-bright`, `--text-dim`, `--text-faint`, `--parch` (inverted blocks), `--c-community`, `--c-founder`, `--c-practitioner` +- **Typography:** Brygada 1918 (serif, display/headings) + Commit Mono (monospace, body/UI/everything structural) — loaded via Google Fonts in `nuxt.config.ts` +- **Theme:** `primary: amber`, `neutral: stone` — configured in `app/app.config.ts`. Tailwind `@theme` maps `--font-sans` and `--font-mono` to Commit Mono, `--font-display` to Brygada 1918 +- **Key classes:** `.btn` / `.btn-primary` / `.btn-danger` (buttons), `.field` (form groups), `.badge` (circle badges), `.section-label` (10px uppercase headers), `.dashed-box` (bordered containers), `.section-divider` +- **Visual language:** Dashed borders (1px dashed), cream backgrounds, no rounded corners, text-forward density, minimal decoration +- **Color mode:** `@nuxtjs/color-mode` with preference `system`, fallback `light`. Dark mode via `.dark` class on `` +- **Layouts:** `default` (sidebar + main, member/public pages), `admin` (sidebar + main, admin pages), `landing` (horizontal nav, unused) + +### Environment + +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. + +## Conventions + +- All frontend code is plain JavaScript (not TypeScript), using Vue 3 Composition API +- Server utilities auto-imported by Nitro — no explicit imports needed in API routes +- Use `USwitch` (not `UToggle`) — this is the correct Nuxt UI 3+ component name +- No fallback/placeholder data — always use real data +- Follow Nuxt 4 file-based routing conventions for route naming +- Always check Nuxt UI 4 latest documentation on the web when implementing UI components +- Auth API responses (`/api/auth/status`, `/api/auth/member`) must include `status` in the returned member object — `useMemberStatus` defaults to `PENDING_PAYMENT` if missing +- Helcim payment testing requires ngrok: `npx nuxi dev --https` then `ngrok http https://localhost:3000` — Helcim blocks localhost origins +- The `/api/helcim/initialize-payment` endpoint skips auth for `event_ticket` type payments (public users can buy tickets) + +## Product Spec + +The sections below describe planned and in-progress features for reference. + +### 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 + +### Events System +- RSVP with capacity limits and waitlist management +- Calendar export (.ics), ticketing, series passes +- Member-proposed events with interest threshold + +### Resources (Planned) +- Learning paths by circle, templates and tools, case studies +- Tag by circle relevance, download tracking, version control diff --git a/Dockerfile b/Dockerfile index 0375bac..25498d8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,12 @@ -# Build stage -FROM node:22-alpine AS builder +# Dockerfile +FROM node:20-alpine WORKDIR /app COPY package*.json ./ -RUN npm ci --ignore-scripts && npx nuxt prepare +RUN npm ci + COPY . . RUN npm run build -# Production stage — only the self-contained .output is needed. -# bash + curl are added so Dokploy scheduled tasks (which wrap commands in -# `bash -c "..."`) can run; alpine ships only ash and has no curl by default. -FROM node:22-alpine -RUN apk add --no-cache bash curl -WORKDIR /app -COPY --from=builder /app/.output .output - EXPOSE 3000 CMD ["node", ".output/server/index.mjs"] diff --git a/app/assets/css/fonts.css b/app/assets/css/fonts.css index 62767ff..9b5bae6 100644 --- a/app/assets/css/fonts.css +++ b/app/assets/css/fonts.css @@ -1,16 +1,8 @@ /* - * Self-hosted font declarations for Ghost Guild — Zine Direction + * Font declarations for Ghost Guild — Zine Direction * - * Brygada 1918: Display/heading serif - * Commit Mono: Body/UI monospace + * Brygada 1918: Display/heading serif (Google Fonts, variable 400-700, italic) + * Commit Mono: Body/UI monospace (Google Fonts) * - * Fonts are bundled locally via Fontsource. + * Loaded via Google Fonts link in nuxt.config.ts head. */ - -@import "@fontsource-variable/brygada-1918/wght.css"; -@import "@fontsource-variable/brygada-1918/wght-italic.css"; - -@import "@fontsource/commit-mono/400.css"; -@import "@fontsource/commit-mono/500.css"; -@import "@fontsource/commit-mono/600.css"; -@import "@fontsource/commit-mono/700.css"; diff --git a/app/assets/css/main.css b/app/assets/css/main.css index 9ee189f..b4f6ac2 100644 --- a/app/assets/css/main.css +++ b/app/assets/css/main.css @@ -15,42 +15,31 @@ :root { --bg: #f4efe4; - --input-bg: #faf8f2; --surface: #e8dfc8; --surface-hover: #e0d6bc; --border: #b8a880; --border-d: #a89470; --candle: #7a5a10; - --candle-dim: #866518; + --candle-dim: #9a7420; --candle-faint: #c4a448; --ember: #8a4420; --text: #2a2015; --text-bright: #1a1008; --text-dim: #5a5040; - /* Darkened from #746a58 (4.01:1 on --surface, fails WCAG AA) to #665c4b - (4.94:1 on --surface, 5.13:1 on --bg). Stays visually quieter than - --text-dim (5.80:1) while meeting AA for small text. */ - --text-faint: #665c4b; + --text-faint: #8a7e6a; --parch: #2a2015; --parch-hover: #3a3025; --parch-text: #ede4d0; --parch-text-dim: #b8ae98; - --parch-accent: #c4a448; - --parch-border: #b8a880; --c-community: #7a4838; --c-founder: #8a4420; --c-practitioner: #2a4650; --green: #4a6a38; --green-bg: rgba(74, 106, 56, 0.08); - --ember-bg: rgba(138, 68, 32, 0.1); - --page-pad-x: 28px; - --page-pad-y: 24px; - --page-collapse: 1024px; } .dark { --bg: #131210; - --input-bg: #1c1a17; --surface: #1a1815; --surface-hover: #252220; --border: #2a2520; @@ -58,33 +47,28 @@ --candle: #d4a03a; --candle-dim: #b8922e; --candle-faint: #8a7030; - --ember: #ca6a3a; + --ember: #c06030; --text: #a89880; --text-bright: #d0c8b0; - --text-dim: #958774; - --text-faint: #8b7b62; - /* Parch family intentionally stays pinned to light-mode values — - inverted blocks are a consistent zine/terminal inset in both themes. - See: --parch-accent and --parch-border for on-parch accents/borders. */ + --text-dim: #8a7e6a; + --text-faint: #5a5040; + --parch: #ede4d0; + --parch-hover: #d4c8a8; + --parch-text: #2a2015; + --parch-text-dim: #5a5040; --c-community: #a06850; --c-founder: #c06030; --c-practitioner: #4a7080; - --green: #6e9c52; - --green-bg: rgba(110, 156, 82, 0.12); - --ember-bg: rgba(202, 106, 58, 0.14); - --page-pad-x: 28px; - --page-pad-y: 24px; - --page-collapse: 1024px; } /* ---- TAILWIND @THEME MAPPING ---- */ @theme { - --font-sans: "Commit Mono", monospace; - --font-body: "Commit Mono", monospace; - --font-mono: "Commit Mono", monospace; - --font-display: "Brygada 1918", serif; - --font-serif: "Brygada 1918", serif; + --font-sans: 'Commit Mono', monospace; + --font-body: 'Commit Mono', monospace; + --font-mono: 'Commit Mono', monospace; + --font-display: 'Brygada 1918', serif; + --font-serif: 'Brygada 1918', serif; /* Map primary to candle for Nuxt UI components */ --color-primary-500: var(--candle); @@ -97,35 +81,14 @@ body { background: var(--bg); color: var(--text); - font-family: "Commit Mono", monospace; + font-family: 'Commit Mono', monospace; font-size: 13px; line-height: 1.6; -webkit-font-smoothing: antialiased; } -/* ---- NOISE TEXTURE OVERLAY ---- */ -body::after { - content: ""; - position: fixed; - inset: 0; - z-index: 9999; - pointer-events: none; - background: url("~/assets/images/noise.webp") repeat; - opacity: 0.025; -} - -a { - color: var(--candle); - text-decoration: none; -} -a:hover { - text-decoration: underline; -} - -p a, blockquote a { - text-decoration: underline; - text-underline-offset: 2px; -} +a { color: var(--candle); text-decoration: none; } +a:hover { text-decoration: underline; } /* ---- SECTION LABELS ---- */ .section-label { @@ -145,26 +108,14 @@ p a, blockquote a { padding: 2px 8px; border: 1px dashed; } -.badge.community { - color: var(--c-community); - border-color: rgba(122, 72, 56, 0.35); -} -.badge.founder { - color: var(--c-founder); - border-color: rgba(138, 68, 32, 0.35); -} -.badge.practitioner { - color: var(--c-practitioner); - border-color: rgba(42, 70, 80, 0.35); -} -.badge.all { - color: var(--text-dim); - border-color: var(--border); -} +.badge.community { color: var(--c-community); border-color: rgba(122, 72, 56, 0.35); } +.badge.founder { color: var(--c-founder); border-color: rgba(138, 68, 32, 0.35); } +.badge.practitioner { color: var(--c-practitioner); border-color: rgba(42, 70, 80, 0.35); } +.badge.all { color: var(--text-dim); border-color: var(--border); } /* ---- BUTTONS ---- */ .btn { - font-family: "Commit Mono", monospace; + font-family: 'Commit Mono', monospace; font-size: 12px; padding: 7px 18px; border: 1px dashed var(--border); @@ -174,26 +125,14 @@ p a, blockquote a { letter-spacing: 0.04em; transition: all 0.15s; } -.btn:hover { - background: var(--surface-hover); - border-color: var(--border-d); -} -/* WCAG 2.4.7 — keyboard focus must be visibly indicated. Dashed outline - echoes the design system's zine/dashed aesthetic. */ -.btn:focus-visible { - outline: 2px dashed var(--candle); - outline-offset: 3px; -} +.btn:hover { background: var(--surface-hover); border-color: var(--border-d); } .btn-primary { background: var(--candle); color: var(--bg); border-color: var(--candle); border-style: solid; } -.btn-primary:hover { - background: var(--candle-dim); - border-color: var(--candle-dim); -} +.btn-primary:hover { background: var(--candle-dim); border-color: var(--candle-dim); } .btn-danger { color: var(--ember); border-color: var(--ember); @@ -205,9 +144,7 @@ p a, blockquote a { } /* ---- FORM FIELDS ---- */ -.field { - margin-bottom: 12px; -} +.field { margin-bottom: 12px; } .field label { font-size: 10px; letter-spacing: 0.08em; @@ -216,21 +153,17 @@ p a, blockquote a { margin-bottom: 3px; display: block; } -.field input, -.field select, -.field textarea { +.field input, .field select, .field textarea { width: 100%; padding: 5px 8px; - font-family: "Commit Mono", monospace; + font-family: 'Commit Mono', monospace; font-size: 13px; color: var(--text-bright); - background: var(--input-bg); + background: var(--bg); border: 1px dashed var(--border); outline: none; } -.field input:focus, -.field select:focus, -.field textarea:focus { +.field input:focus, .field select:focus, .field textarea:focus { border-color: var(--candle); border-style: solid; } @@ -241,25 +174,8 @@ p a, blockquote a { padding: 20px 24px; transition: border-color 0.2s; } -.dashed-box:hover { - border-color: var(--candle-faint); -} -.dashed-box.no-hover:hover { - border-color: var(--border); -} - -/* ---- SEGMENTED CONTROL (flush dashed-border groups) ---- */ -/* Negative-margin overlap: every item keeps all 4 borders, - siblings overlap by 1px, active item paints on top via z-index. */ -.segmented { - display: flex; -} -.segmented > * { - position: relative; -} -.segmented > * + * { - margin-left: -1px; -} +.dashed-box:hover { border-color: var(--candle-faint); } +.dashed-box.no-hover:hover { border-color: var(--border); } /* ---- SECTION DIVIDERS ---- */ .section-divider { @@ -276,98 +192,6 @@ p a, blockquote a { min-width: 0; } -/* ---- Nuxt UI placeholder contrast ---- - Default Nuxt UI placeholder uses `text-dimmed` (#a6a09b) which fails WCAG - AA on cream and white backgrounds (≈2.4:1). Override globally to --text-dim - so USelect/USelectMenu placeholders meet the 4.5:1 ratio. */ -[data-slot="placeholder"] { - color: var(--text-dim); -} - -/* ---- SHARED USelectMenu STYLES ---- - Apply via: - Classes are global (not scoped) because Nuxt UI portals the popup content to body. */ -button.zine-select, -button.timezone-select { - display: flex !important; - width: 100%; - padding: 5px 8px !important; - font-family: "Commit Mono", monospace !important; - font-size: 13px !important; - color: var(--text-bright) !important; - background: var(--input-bg) !important; - border: 1px solid var(--border) !important; - border-radius: 0 !important; - box-shadow: none !important; - outline: none !important; - min-height: 0; - --tw-ring-shadow: 0 0 #0000; - --tw-ring-offset-shadow: 0 0 #0000; - --tw-ring-color: transparent; -} - -button.zine-select:hover, -button.timezone-select:hover { - background: var(--input-bg) !important; -} - -button.zine-select:focus, -button.zine-select:focus-visible, -button.zine-select[aria-expanded="true"], -button.timezone-select:focus, -button.timezone-select:focus-visible, -button.timezone-select[aria-expanded="true"] { - border-color: var(--candle) !important; -} - -.tz-content { - background: var(--input-bg) !important; - border: 1px solid var(--border) !important; - border-radius: 0 !important; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12) !important; - --tw-ring-shadow: 0 0 #0000 !important; - --tw-ring-offset-shadow: 0 0 #0000 !important; - font-family: "Commit Mono", monospace !important; -} - -.tz-input { - border-bottom: 1px dashed var(--border) !important; -} - -.tz-input input { - font-family: "Commit Mono", monospace !important; - font-size: 13px !important; - color: var(--text-bright) !important; - background: transparent !important; - border-radius: 0 !important; - padding: 6px 8px !important; - box-shadow: none !important; - --tw-ring-shadow: 0 0 #0000 !important; - --tw-ring-offset-shadow: 0 0 #0000 !important; -} - -.tz-item { - font-family: "Commit Mono", monospace !important; - font-size: 13px !important; - color: var(--text) !important; - border-radius: 0 !important; - padding: 6px 8px !important; -} - -.tz-item::before { - border-radius: 0 !important; -} - -.tz-item[data-highlighted]::before, -.tz-item[data-highlighted]:not([data-disabled])::before { - background: var(--surface-hover) !important; -} - -.tz-item[data-highlighted], -.tz-item[data-highlighted]:not([data-disabled]) { - color: var(--text-bright) !important; -} - /* ---- MOBILE ---- */ @media (max-width: 1023px) { body { diff --git a/app/assets/images/noise.webp b/app/assets/images/noise.webp deleted file mode 100644 index d9f0fa2..0000000 Binary files a/app/assets/images/noise.webp and /dev/null differ diff --git a/app/components/AppNavigation.vue b/app/components/AppNavigation.vue index 07cc322..6a82cd0 100644 --- a/app/components/AppNavigation.vue +++ b/app/components/AppNavigation.vue @@ -17,36 +17,29 @@ :to="item.path" :class="{ active: isActive(item.path) }" @click="handleNavigate" - > - {{ item.label }} - - + >{{ item.label }} + + + @@ -56,23 +49,11 @@ @@ -83,8 +64,7 @@ :to="item.path" :class="{ active: isActive(item.path) }" @click="handleNavigate" - >{{ item.label }} + >{{ item.label }} @@ -94,23 +74,11 @@ @@ -121,8 +89,7 @@ :to="item.path" :class="{ active: isActive(item.path) }" @click="handleNavigate" - >{{ item.label }} + >{{ item.label }} @@ -132,17 +99,29 @@ @@ -155,59 +134,68 @@ const props = defineProps({ type: Boolean, default: false, }, -}); +}) -const emit = defineEmits(["navigate"]); +const emit = defineEmits(['navigate']) -const route = useRoute(); -const { isAuthenticated, memberData, logout } = useAuth(); -const isDev = import.meta.dev; - -const showOnboardingDot = computed( - () => isAuthenticated.value && !memberData.value?.onboarding?.completedAt, -); +const route = useRoute() +const { isAuthenticated, logout, memberData } = useAuth() +const { openLoginModal } = useLoginModal() const handleNavigate = () => { if (props.isMobile) { - emit("navigate"); + emit('navigate') } -}; +} const handleLogout = async () => { - await logout(); - handleNavigate(); - navigateTo("/"); -}; + await logout() + handleNavigate() +} + +const openLogin = () => { + openLoginModal() + handleNavigate() +} const isActive = (path) => { - if (path === "/") return route.path === "/"; - return route.path.startsWith(path); -}; + if (path === '/') return route.path === '/' + return route.path.startsWith(path) +} // Public nav items const publicItems = [ - { label: "Home", path: "/" }, - { label: "About", path: "/about" }, - { label: "Events", path: "/events" }, - { label: "Wiki", path: "https://wiki.ghostguild.org", external: true }, -]; + { label: 'Home', path: '/' }, + { label: 'About', path: '/about' }, + { label: 'Events', path: '/events' }, + { label: 'Members', path: '/members' }, + { label: 'Wiki', path: '/wiki' }, +] -const joinItems = [{ label: "Become a member", path: "/join" }]; +const joinItems = [ + { label: 'Become a member', path: '/join' }, + { label: 'Propose an event', path: '/events' }, +] // Logged-in nav items const youItems = [ - { label: "Dashboard", path: "/member/dashboard" }, - { label: "Profile", path: "/member/profile" }, - { label: "Account", path: "/member/account" }, -]; + { label: 'Dashboard', path: '/member/dashboard' }, + { label: 'Profile', path: '/member/profile' }, + { label: 'Account', path: '/member/account' }, + { label: 'My Updates', path: '/member/my-updates' }, +] const exploreItems = [ - { label: "Events", path: "/events" }, - { label: "Members", path: "/members" }, - { label: "Board", path: "/board" }, - { label: "Wiki", path: "https://wiki.ghostguild.org", external: true }, - { label: "About", path: "/about" }, -]; + { label: 'Events', path: '/events' }, + { label: 'Members', path: '/members' }, + { label: 'Wiki', path: '/wiki' }, + { label: 'About', path: '/about' }, +] + +const communityItems = [ + { label: 'Peer Support', path: '/members' }, + { label: 'Propose an Event', path: '/events' }, +] diff --git a/app/components/BoardPostCard.vue b/app/components/BoardPostCard.vue deleted file mode 100644 index a79a535..0000000 --- a/app/components/BoardPostCard.vue +++ /dev/null @@ -1,386 +0,0 @@ - - - - - diff --git a/app/components/BoardPostForm.vue b/app/components/BoardPostForm.vue deleted file mode 100644 index 981475a..0000000 --- a/app/components/BoardPostForm.vue +++ /dev/null @@ -1,265 +0,0 @@ - + + + + diff --git a/app/pages/admin/members/[id].vue b/app/pages/admin/members/[id].vue deleted file mode 100644 index e082be7..0000000 --- a/app/pages/admin/members/[id].vue +++ /dev/null @@ -1,858 +0,0 @@ - - - - - diff --git a/app/pages/admin/members/index.vue b/app/pages/admin/members/index.vue deleted file mode 100644 index d602447..0000000 --- a/app/pages/admin/members/index.vue +++ /dev/null @@ -1,1387 +0,0 @@ - - - - - diff --git a/app/pages/admin/series-management.vue b/app/pages/admin/series-management.vue index c558d3a..98c482c 100644 --- a/app/pages/admin/series-management.vue +++ b/app/pages/admin/series-management.vue @@ -64,7 +64,7 @@
{{ formatSeriesType(series.type) }} -

{{ series.title }}

+

{{ series.title }}

{{ series.description }}

@@ -112,6 +112,7 @@ +
@@ -170,7 +171,15 @@