Documentation editorial: Material theme hardening, metadata, and content polish
- Enable navigation.instant, prefetch, progress, content.code.select, content.tabs.link - Fix edit_uri (main→v2), copyright year (2024→2024-2026), consent banner config - Add abbreviations glossary (47 acronyms with hover tooltips via snippets auto-append) - Add tags to all 72 doc pages with consistent taxonomy (audience/module/type) - Add status:new badges to 16 recent feature pages, search:boost to 7 entry pages - Rewrite Architecture page with 5 Mermaid diagrams and full component documentation - Rewrite Troubleshooting page from 5 to 13 sections with actionable checklists - Fix broken links (Monitoring/Contributing pointed to blog placeholder) - Expand Admin Guide roles table from 5 to 11 roles - Create custom 404 page, blog with authors and inaugural v2 announcement post - Fresh Playwright screenshots for login, dashboard, campaigns, users, settings, locations, shifts - Remove 5 test/dev files and orphan template override - Add planning document (DOCS_NEXT_STEPS.md) for future editorial reference Bunker Admin
305
mkdocs/DOCS_NEXT_STEPS.md
Normal file
@ -0,0 +1,305 @@
|
||||
# Documentation Next Steps — Editorial & Material Theme Enhancement Plan
|
||||
|
||||
**Date:** 2026-03-22
|
||||
**Branch:** v2
|
||||
**Status:** Planning → Execution
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
The MkDocs documentation site is comprehensive (70+ pages, all with proper frontmatter, no stubs) and already uses many Material theme features well (grid cards, admonitions, code copy, mermaid diagrams, social cards, blog plugin, dark/light toggle). This plan focuses on **activating dormant Material theme capabilities** and **editorial polish** to take the docs from "complete" to "professional-grade."
|
||||
|
||||
---
|
||||
|
||||
## Step 1: mkdocs.yml Configuration Hardening
|
||||
|
||||
**Goal:** Enable Material theme features that are available but not configured.
|
||||
|
||||
### 1a. Add missing theme features
|
||||
```yaml
|
||||
features:
|
||||
# Already enabled (keep):
|
||||
- announce.dismiss
|
||||
- content.action.edit
|
||||
- content.action.view
|
||||
- content.code.annotate
|
||||
- content.code.copy
|
||||
- content.tooltips
|
||||
- navigation.footer
|
||||
- navigation.indexes
|
||||
- navigation.path
|
||||
- navigation.prune
|
||||
- navigation.tabs
|
||||
- navigation.tabs.sticky
|
||||
- navigation.top
|
||||
- navigation.tracking
|
||||
- search.highlight
|
||||
- search.share
|
||||
- search.suggest
|
||||
- toc.follow
|
||||
|
||||
# ADD these:
|
||||
- navigation.instant # SPA-like navigation (no full page reload)
|
||||
- navigation.instant.prefetch # Prefetch pages on hover
|
||||
- navigation.instant.progress # Show loading progress bar
|
||||
- content.code.select # Line selection in code blocks
|
||||
- content.tabs.link # Linked content tabs (sync across page)
|
||||
```
|
||||
|
||||
### 1b. Fix consent banner
|
||||
The copyright references `#__consent` but no consent config exists. Add:
|
||||
```yaml
|
||||
extra:
|
||||
consent:
|
||||
title: Cookie consent
|
||||
description: >
|
||||
We use cookies to recognize your repeated visits and preferences,
|
||||
as well as to measure the effectiveness of our documentation.
|
||||
With your consent, you help us improve.
|
||||
actions:
|
||||
- accept
|
||||
- reject
|
||||
- manage
|
||||
```
|
||||
|
||||
### 1c. Fix copyright year
|
||||
Change `2024` → `2024–2026` in the copyright line.
|
||||
|
||||
### 1d. Fix edit_uri branch
|
||||
Change `edit_uri: src/branch/main/mkdocs/docs` → `edit_uri: src/branch/v2/mkdocs/docs`
|
||||
|
||||
### 1e. Add abbreviations snippets
|
||||
Create `docs/includes/abbreviations.md` and reference via snippets:
|
||||
```yaml
|
||||
markdown_extensions:
|
||||
- pymdownx.snippets:
|
||||
auto_append:
|
||||
- includes/abbreviations.md
|
||||
```
|
||||
|
||||
Common abbreviations: API, JWT, RBAC, CSV, ORM, SMTP, CORS, SSL, TLS, DNS, CRUD, SSO, SPA, CLI, GUI, QR, GPS, GDPR, CDN, VPS, CGNAT, NAR, CRM, OG, DDoS, SSE, UUID, FOSS, HSTS, CSP
|
||||
|
||||
### 1f. Add privacy plugin (optional, external resource proxying)
|
||||
```yaml
|
||||
plugins:
|
||||
- privacy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Page-Level Metadata Enhancement
|
||||
|
||||
**Goal:** Add `tags`, `status`, and `search.boost` metadata to every page.
|
||||
|
||||
### 2a. Tags
|
||||
The tags plugin is loaded but zero pages use tags. Add contextually appropriate tags to every page. Example tag taxonomy:
|
||||
|
||||
- **Audience:** `admin`, `volunteer`, `user`, `operator`, `developer`
|
||||
- **Module:** `influence`, `map`, `media`, `payments`, `broadcast`, `social`
|
||||
- **Type:** `guide`, `reference`, `tutorial`, `concept`, `troubleshooting`
|
||||
- **Feature:** `campaigns`, `canvassing`, `shifts`, `gallery`, `landing-pages`, `newsletter`, `sms`, `chat`, `events`
|
||||
|
||||
### 2b. Status badges
|
||||
Material supports `status: new` and `status: deprecated` on pages, shown as badges in the nav sidebar. Apply:
|
||||
- `status: new` — Recent features (Gallery Ads, People CRM, Achievements, Social Calendar, CrowdSec, SMS, Docs Comments, Payments)
|
||||
- `status: deprecated` — Legacy/archival content if any
|
||||
|
||||
Requires adding to `extra:` in mkdocs.yml:
|
||||
```yaml
|
||||
extra:
|
||||
status:
|
||||
new: Recently added
|
||||
deprecated: Legacy
|
||||
```
|
||||
|
||||
### 2c. Search boost
|
||||
Boost important entry-point pages so they rank higher:
|
||||
```yaml
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
```
|
||||
Apply to: Getting Started index, Installation, First Steps, Features at a Glance, FAQ/Troubleshooting.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Abbreviations Glossary
|
||||
|
||||
**Goal:** Create a shared abbreviations file so that hovering over acronyms shows their meaning.
|
||||
|
||||
Create `mkdocs/docs/includes/abbreviations.md`:
|
||||
```markdown
|
||||
*[API]: Application Programming Interface
|
||||
*[JWT]: JSON Web Token
|
||||
*[RBAC]: Role-Based Access Control
|
||||
*[CORS]: Cross-Origin Resource Sharing
|
||||
*[SMTP]: Simple Mail Transfer Protocol
|
||||
*[CSV]: Comma-Separated Values
|
||||
*[ORM]: Object-Relational Mapping
|
||||
*[SSL]: Secure Sockets Layer
|
||||
*[TLS]: Transport Layer Security
|
||||
*[DNS]: Domain Name System
|
||||
*[CRUD]: Create, Read, Update, Delete
|
||||
*[SSO]: Single Sign-On
|
||||
*[SPA]: Single Page Application
|
||||
*[CLI]: Command Line Interface
|
||||
*[GUI]: Graphical User Interface
|
||||
*[QR]: Quick Response (code)
|
||||
*[GPS]: Global Positioning System
|
||||
*[GDPR]: General Data Protection Regulation
|
||||
*[CDN]: Content Delivery Network
|
||||
*[VPS]: Virtual Private Server
|
||||
*[CGNAT]: Carrier-Grade Network Address Translation
|
||||
*[NAR]: National Address Register
|
||||
*[CRM]: Customer Relationship Management
|
||||
*[OG]: Open Graph
|
||||
*[DDoS]: Distributed Denial of Service
|
||||
*[SSE]: Server-Sent Events
|
||||
*[UUID]: Universally Unique Identifier
|
||||
*[FOSS]: Free and Open Source Software
|
||||
*[HSTS]: HTTP Strict Transport Security
|
||||
*[CSP]: Content Security Policy
|
||||
*[BullMQ]: Bull Message Queue
|
||||
*[FFprobe]: FFmpeg Probe (media metadata tool)
|
||||
*[FFmpeg]: Fast Forward Moving Picture Experts Group
|
||||
*[XMPP]: Extensible Messaging and Presence Protocol
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Custom 404 Page
|
||||
|
||||
**Goal:** Branded 404 page instead of browser default.
|
||||
|
||||
Create `mkdocs/docs/404.md`:
|
||||
```markdown
|
||||
---
|
||||
template: main.html
|
||||
title: Page Not Found
|
||||
hide:
|
||||
- navigation
|
||||
- toc
|
||||
- footer
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
|
||||
# Page Not Found
|
||||
|
||||
The page you're looking for doesn't exist or has been moved.
|
||||
|
||||
[Go to Documentation Home](docs/index.md){ .md-button .md-button--primary }
|
||||
[Search](docs/index.md){ .md-button }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Content Fixes — Broken Links & Stale Warnings
|
||||
|
||||
**Goal:** Fix all broken links, placeholder content, and stale warnings.
|
||||
|
||||
### 5a. Fix broken "Coming soon" links
|
||||
- `docs/index.md`: Monitoring card links to `../blog/index.md` → link to `admin/services/monitoring.md`
|
||||
- `docs/index.md`: Contributing card links to `../blog/index.md` → create a contributing stub or remove placeholder
|
||||
|
||||
### 5b. Fix Architecture page
|
||||
Remove "Under Construction" admonition. Flesh out with:
|
||||
- Mermaid system diagram
|
||||
- Database entity relationship summary
|
||||
- Authentication flow (already started)
|
||||
- Request lifecycle
|
||||
|
||||
### 5c. Fix Troubleshooting page
|
||||
Remove "Under Construction" admonition. Add more entries from CLAUDE.md and production experience.
|
||||
|
||||
### 5d. Fix Admin Guide roles table
|
||||
Only lists 5 roles but there are 11. Add missing roles:
|
||||
BROADCAST_ADMIN, CONTENT_ADMIN, MEDIA_ADMIN, PAYMENTS_ADMIN, EVENTS_ADMIN, SOCIAL_ADMIN
|
||||
|
||||
### 5e. Social icons semantic fix
|
||||
GitHub icon links to Gitea — change icon to `fontawesome/brands/gitea` or `simple/gitea` if available, or use a generic `fontawesome/solid/code-branch`.
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Cleanup Test/Orphan Files
|
||||
|
||||
**Goal:** Remove files that shouldn't be in the docs directory.
|
||||
|
||||
- `docs/test.md` (41KB test file)
|
||||
- `docs/test-page.md`
|
||||
- `docs/testing.md`
|
||||
- `docs/lander.md` (override template pointer — keep if used by index.md)
|
||||
- `docs/main.md` (override template pointer — keep if used)
|
||||
|
||||
Verify `lander.md` and `main.md` are still needed by checking if any page uses `template: lander.html` or `template: main.html`.
|
||||
|
||||
---
|
||||
|
||||
## Step 7: Announcement Bar
|
||||
|
||||
**Goal:** Use Material's announcement bar for version/status info.
|
||||
|
||||
Create `mkdocs/docs/overrides/main.html` addition (or modify existing) with announcement block:
|
||||
```html
|
||||
{% block announce %}
|
||||
Changemaker Lite v2 — <a href="docs/getting-started/index.md">Get started in 5 minutes</a>
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
Note: The existing `main.html` override is 35KB — check if it already has an announce block before modifying.
|
||||
|
||||
---
|
||||
|
||||
## Step 8: Blog Seeding
|
||||
|
||||
**Goal:** Create 1–2 initial blog posts so the blog section isn't empty.
|
||||
|
||||
Suggested posts:
|
||||
1. **"Introducing Changemaker Lite v2"** — Overview of the rebuild, what's new, philosophy
|
||||
2. **"Why Self-Hosted Campaign Tools Matter in 2026"** — Draws from the Philosophy page
|
||||
|
||||
---
|
||||
|
||||
## Step 9: Screenshot Audit
|
||||
|
||||
**Goal:** Verify all `` references resolve to actual files. Take new screenshots with Playwright where missing or outdated.
|
||||
|
||||
Pages referencing screenshots:
|
||||
- Getting Started index (dashboard.png)
|
||||
- First Steps (login.png, dashboard.png, settings-organization.png, campaigns.png, locations.png, shifts.png)
|
||||
- Dashboard (dashboard.png)
|
||||
- People & Access (users.png)
|
||||
- Settings (settings.png)
|
||||
- Campaigns (campaigns.png)
|
||||
|
||||
---
|
||||
|
||||
## Priority Order
|
||||
|
||||
| Priority | Step | Impact | Effort |
|
||||
|----------|------|--------|--------|
|
||||
| **P0** | Step 1 (mkdocs.yml fixes) | High — broken consent, wrong branch, missing SPA nav | Low |
|
||||
| **P0** | Step 5 (broken links/content) | High — broken UX | Medium |
|
||||
| **P0** | Step 6 (cleanup test files) | Medium — professional appearance | Low |
|
||||
| **P1** | Step 3 (abbreviations) | Medium — reader comprehension | Low |
|
||||
| **P1** | Step 4 (404 page) | Medium — UX polish | Low |
|
||||
| **P1** | Step 2 (tags/status/boost) | Medium — discoverability | Medium-High |
|
||||
| **P2** | Step 7 (announcement bar) | Low-Medium — branding | Low |
|
||||
| **P2** | Step 8 (blog seeding) | Low — completeness | Medium |
|
||||
| **P2** | Step 9 (screenshot audit) | Medium — visual credibility | High |
|
||||
|
||||
---
|
||||
|
||||
## Material Theme Reference
|
||||
|
||||
Key Material docs for implementers:
|
||||
- [Setting up tags](https://squidfunk.github.io/mkdocs-material/setup/setting-up-tags/)
|
||||
- [Setting up navigation](https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/)
|
||||
- [Page status](https://squidfunk.github.io/mkdocs-material/reference/index/#setting-the-page-status)
|
||||
- [Search boosting](https://squidfunk.github.io/mkdocs-material/setup/setting-up-site-search/#search-boosting)
|
||||
- [Abbreviations](https://squidfunk.github.io/mkdocs-material/reference/tooltips/#adding-abbreviations)
|
||||
- [Cookie consent](https://squidfunk.github.io/mkdocs-material/setup/ensuring-data-privacy/#cookie-consent)
|
||||
- [Custom 404](https://www.mkdocs.org/user-guide/custom-themes/#custom-theme)
|
||||
- [Announcement bar](https://squidfunk.github.io/mkdocs-material/setup/setting-up-the-header/#announcement-bar)
|
||||
25
mkdocs/docs/404.md
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
template: main.html
|
||||
title: Page Not Found
|
||||
hide:
|
||||
- navigation
|
||||
- toc
|
||||
- footer
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
|
||||
<div style="text-align: center; padding: 4rem 1rem;" markdown>
|
||||
|
||||
# :material-map-marker-question: Page Not Found
|
||||
|
||||
The page you're looking for doesn't exist or has been moved.
|
||||
|
||||
[Back to Documentation :material-arrow-left:](docs/index.md){ .md-button .md-button--primary }
|
||||
[Search the Docs :material-magnify:](docs/index.md){ .md-button }
|
||||
|
||||
---
|
||||
|
||||
*If you followed a link here, please [report the broken link](https://gitea.bnkops.com/admin/changemaker.lite/issues/new){ target="_blank" } so we can fix it.*
|
||||
|
||||
</div>
|
||||
|
Before Width: | Height: | Size: 435 KiB After Width: | Height: | Size: 179 KiB |
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 179 KiB After Width: | Height: | Size: 179 KiB |
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 179 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
5
mkdocs/docs/blog/.authors.yml
Normal file
@ -0,0 +1,5 @@
|
||||
authors:
|
||||
admin:
|
||||
name: Bunker Operations
|
||||
description: Changemaker Lite core team
|
||||
avatar: https://gitea.bnkops.com/avatars/1
|
||||
71
mkdocs/docs/blog/posts/introducing-changemaker-lite-v2.md
Normal file
@ -0,0 +1,71 @@
|
||||
---
|
||||
date: 2026-03-22
|
||||
authors:
|
||||
- admin
|
||||
categories:
|
||||
- Announcements
|
||||
- Platform
|
||||
tags:
|
||||
- v2
|
||||
- release
|
||||
- self-hosted
|
||||
- FOSS
|
||||
---
|
||||
|
||||
# Introducing Changemaker Lite v2
|
||||
|
||||
Changemaker Lite v2 is a ground-up rebuild of the platform — same mission, entirely new architecture. After 14 phases of development, the platform is ready for production use.
|
||||
|
||||
<!-- more -->
|
||||
|
||||
## What Changed
|
||||
|
||||
V1 was two independent Express apps stitched together with NocoDB as a data layer. It worked, but scaling features meant fighting the architecture at every turn.
|
||||
|
||||
V2 is a unified TypeScript stack:
|
||||
|
||||
- **Dual API architecture** — Express.js for the main platform, Fastify for the media library, sharing a single PostgreSQL 16 database via Prisma ORM
|
||||
- **React admin GUI** — Vite + Ant Design + Zustand, serving admin, public, and volunteer interfaces from one build
|
||||
- **30+ Docker services** — from core infrastructure to monitoring, communication, and developer tools
|
||||
- **JWT authentication** with refresh token rotation, role-based access control (11 roles), and a comprehensive security audit
|
||||
|
||||
## What's New
|
||||
|
||||
The feature set has grown substantially:
|
||||
|
||||
- **Advocacy campaigns** with postal code → representative lookup, email sending, response walls, and moderation
|
||||
- **Map & canvassing** with multi-provider geocoding, polygon territories, GPS-tracked volunteer sessions, and walking route generation
|
||||
- **Media manager** with video upload, FFprobe metadata extraction, scheduled publishing, analytics, and a public gallery
|
||||
- **Landing page builder** powered by GrapesJS with drag-and-drop editing
|
||||
- **Payments** via encrypted Stripe integration — products, donations, and subscription plans
|
||||
- **SMS campaigns** via a Termux Android bridge
|
||||
- **Team communication** with self-hosted Rocket.Chat and Jitsi Meet
|
||||
- **People CRM** aggregating contacts across all modules with duplicate detection and merge
|
||||
- **Volunteer social features** — friend system, achievements, leaderboards, and a personal calendar
|
||||
- **One-command install** — `curl | bash` pulls a release tarball and runs the config wizard
|
||||
|
||||
## Why Self-Hosted
|
||||
|
||||
Every subscription to corporate campaign software funds infrastructure you don't control. Your voter lists, canvassing outcomes, and communication patterns become assets on someone else's balance sheet.
|
||||
|
||||
Changemaker Lite costs roughly the price of a VPS — often under $50/month for the full stack. But the real value isn't cost savings. It's **control.** No vendor can cut off your access. No acquisition can change your terms.
|
||||
|
||||
Read more in our [Philosophy](../../docs/phil.md) page.
|
||||
|
||||
## Get Started
|
||||
|
||||
```bash
|
||||
curl -fsSL https://gitea.bnkops.com/admin/changemaker.lite/raw/branch/v2/scripts/install.sh | bash
|
||||
```
|
||||
|
||||
Or follow the [Getting Started guide](../../docs/getting-started/index.md) for a walkthrough.
|
||||
|
||||
## What's Next
|
||||
|
||||
Phase 15 (Testing & Polish) is underway. We're also working on:
|
||||
|
||||
- Social Calendar Phase B (shared views, availability finder)
|
||||
- Expanded test coverage
|
||||
- Performance optimization for large location datasets
|
||||
|
||||
Follow this blog for updates, or subscribe to the [newsletter](https://listmonk.bnkops.com/subscription/form).
|
||||
@ -2,6 +2,11 @@
|
||||
title: Advocacy Campaigns
|
||||
description: Help supporters contact elected representatives through email campaigns with postal code lookup and response tracking.
|
||||
icon: material/email-fast
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- influence
|
||||
- campaigns
|
||||
---
|
||||
|
||||
# Advocacy Campaigns
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Email Queue
|
||||
description: Monitor and manage the BullMQ advocacy email delivery queue.
|
||||
icon: material/email-sync
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- influence
|
||||
- email
|
||||
---
|
||||
|
||||
# Email Queue
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
title: Advocacy
|
||||
description: Manage email campaigns, moderate responses, and monitor email delivery.
|
||||
icon: material/email-fast
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- influence
|
||||
---
|
||||
|
||||
# Advocacy
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
title: Representatives
|
||||
description: Represent API integration for postal code to representative lookup with caching.
|
||||
icon: material/account-tie
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- influence
|
||||
---
|
||||
|
||||
# Representatives
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Response Moderation
|
||||
description: Moderate the public response wall where supporters share how representatives replied.
|
||||
icon: material/message-reply-text
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- influence
|
||||
- moderation
|
||||
---
|
||||
|
||||
# Response Moderation
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Email Templates
|
||||
description: Create reusable email templates with variable substitution for campaign communications.
|
||||
icon: material/email-edit
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- broadcast
|
||||
- email
|
||||
---
|
||||
|
||||
# Email Templates
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
title: Broadcast
|
||||
description: Newsletter sync, email templates, and SMS campaigns for reaching supporters.
|
||||
icon: material/bullhorn
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- broadcast
|
||||
---
|
||||
|
||||
# Broadcast
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Newsletter (Listmonk)
|
||||
description: Integrated opt-in mailing lists and newsletter campaigns powered by Listmonk.
|
||||
icon: material/newspaper-variant
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- broadcast
|
||||
- email
|
||||
---
|
||||
|
||||
# Newsletter (Listmonk)
|
||||
|
||||
@ -2,6 +2,12 @@
|
||||
title: SMS Campaigns
|
||||
description: Complete guide to setting up the Termux Android SMS bridge for text message outreach, contact management, and response tracking.
|
||||
icon: material/message-text
|
||||
status: new
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- broadcast
|
||||
- sms
|
||||
---
|
||||
|
||||
# SMS Campaigns
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
title: Dashboard
|
||||
description: Admin dashboard with live stats, activity feed, upcoming shifts, and service health indicators.
|
||||
icon: material/view-dashboard
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
---
|
||||
|
||||
# Dashboard
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
title: Admin Guide
|
||||
description: Day-to-day administration of users, campaigns, content, maps, media, and platform services.
|
||||
icon: material/shield-account
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
---
|
||||
|
||||
# Admin Guide
|
||||
@ -82,8 +85,14 @@ The admin panel at `/app` is your command center for managing the entire platfor
|
||||
|
||||
| Role | Access Level |
|
||||
|------|-------------|
|
||||
| `SUPER_ADMIN` | Full platform access |
|
||||
| `INFLUENCE_ADMIN` | Campaigns, responses, email queue |
|
||||
| `MAP_ADMIN` | Locations, areas, shifts, canvassing |
|
||||
| `SUPER_ADMIN` | Full platform access — implicitly bypasses all role checks |
|
||||
| `INFLUENCE_ADMIN` | Campaigns, responses, representatives, email queue |
|
||||
| `MAP_ADMIN` | Locations, areas, shifts, canvassing, data quality |
|
||||
| `BROADCAST_ADMIN` | Newsletter sync, email templates |
|
||||
| `CONTENT_ADMIN` | Landing pages, homepage, navigation, documentation |
|
||||
| `MEDIA_ADMIN` | Video library, analytics, gallery, moderation, ads |
|
||||
| `PAYMENTS_ADMIN` | Products, donations, plans, Stripe configuration |
|
||||
| `EVENTS_ADMIN` | Gancio event sync and calendar management |
|
||||
| `SOCIAL_ADMIN` | Social connections, achievements, calendar layers |
|
||||
| `USER` | Volunteer portal only |
|
||||
| `TEMP` | Limited volunteer access (auto-created) |
|
||||
| `TEMP` | Limited volunteer access (auto-created on shift signup) |
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Areas (Cuts)
|
||||
description: Draw polygon canvassing territories on the map and organize locations into manageable regions.
|
||||
icon: material/vector-polygon
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- map
|
||||
- canvassing
|
||||
---
|
||||
|
||||
# Areas (Cuts)
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Canvassing
|
||||
description: Canvass dashboard, walk sheets, session management, and contact export for door-to-door outreach.
|
||||
icon: material/walk
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- map
|
||||
- canvassing
|
||||
---
|
||||
|
||||
# Canvassing
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Data Quality
|
||||
description: Geocoding quality metrics, provider distribution, and confidence analysis.
|
||||
icon: material/chart-box
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- map
|
||||
- analytics
|
||||
---
|
||||
|
||||
# Data Quality
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
title: Map & Canvassing
|
||||
description: Location management, canvassing territories, volunteer shifts, and door-to-door outreach coordination.
|
||||
icon: material/map-marker-multiple
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- map
|
||||
---
|
||||
|
||||
# Map & Canvassing
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Locations
|
||||
description: Import, geocode, and manage addresses for canvassing and public map display.
|
||||
icon: material/map-marker-plus
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- map
|
||||
- locations
|
||||
---
|
||||
|
||||
# Locations
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Map Settings
|
||||
description: Configure map center coordinates, default zoom level, and QR code links for walk sheets.
|
||||
icon: material/cog
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- map
|
||||
- configuration
|
||||
---
|
||||
|
||||
# Map Settings
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Shifts
|
||||
description: Schedule volunteer time slots with recurring patterns, calendar views, and area assignments.
|
||||
icon: material/calendar-clock
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- map
|
||||
- shifts
|
||||
---
|
||||
|
||||
# Shifts
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Gallery Ads
|
||||
description: Promotional cards with audience targeting, scheduling, and click-through analytics.
|
||||
icon: material/advertisements
|
||||
status: new
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- media
|
||||
---
|
||||
|
||||
# Gallery Ads
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Analytics
|
||||
description: Video engagement metrics including views, watch time, completion rates, and traffic sources.
|
||||
icon: material/chart-line
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- media
|
||||
- analytics
|
||||
---
|
||||
|
||||
# Analytics
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Curated Gallery
|
||||
description: Manage playlists, the shorts feed, and featured content for the public video gallery.
|
||||
icon: material/playlist-play
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- media
|
||||
- gallery
|
||||
---
|
||||
|
||||
# Curated Gallery
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
title: Media
|
||||
description: Video and photo library, analytics, playlists, comment moderation, and gallery ad management.
|
||||
icon: material/play-box-multiple
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- media
|
||||
---
|
||||
|
||||
# Media
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Library
|
||||
description: Upload and manage videos and photos with metadata extraction, scheduled publishing, and preview links.
|
||||
icon: material/folder-play
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- media
|
||||
- videos
|
||||
---
|
||||
|
||||
# Library
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Moderation
|
||||
description: Review and manage comments across media content with word filters and moderation tools.
|
||||
icon: material/shield-check
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- media
|
||||
- moderation
|
||||
---
|
||||
|
||||
# Moderation
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Donations
|
||||
description: Create branded donation pages with fundraising goals, suggested amounts, and tracking.
|
||||
icon: material/hand-heart
|
||||
status: new
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- payments
|
||||
---
|
||||
|
||||
# Donations
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Payments
|
||||
description: Stripe-powered products, donations, subscription plans, and payment configuration.
|
||||
icon: material/credit-card
|
||||
status: new
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- payments
|
||||
---
|
||||
|
||||
# Payments
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Plans
|
||||
description: Recurring subscription plans with monthly and yearly billing via Stripe.
|
||||
icon: material/card-account-details-star
|
||||
status: new
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- payments
|
||||
---
|
||||
|
||||
# Plans
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Products
|
||||
description: Manage merchandise and one-time purchase items with inventory and Stripe checkout.
|
||||
icon: material/shopping
|
||||
status: new
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- payments
|
||||
---
|
||||
|
||||
# Products
|
||||
|
||||
@ -2,6 +2,12 @@
|
||||
title: Payment Settings
|
||||
description: Configure Stripe API keys with encrypted storage for secure payment processing.
|
||||
icon: material/credit-card-settings
|
||||
status: new
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- payments
|
||||
- configuration
|
||||
---
|
||||
|
||||
# Payment Settings
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: People & Access
|
||||
description: Manage users, roles, and the unified People CRM.
|
||||
icon: material/account-group
|
||||
status: new
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- CRM
|
||||
---
|
||||
|
||||
# People & Access
|
||||
|
||||
@ -2,6 +2,12 @@
|
||||
title: CrowdSec & Security
|
||||
description: CrowdSec Manager web UI, Tinyauth forward-auth, CrowdSec tuning, and Cloudflare Turnstile captcha on the Pangolin server.
|
||||
icon: material/shield-lock
|
||||
status: new
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- services
|
||||
- security
|
||||
---
|
||||
|
||||
# CrowdSec Manager & Security Configuration
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
title: Services
|
||||
description: Tunnel management, monitoring, and third-party service integrations.
|
||||
icon: material/server-network
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- services
|
||||
---
|
||||
|
||||
# Services
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Integrations
|
||||
description: Chat, video conferencing, password manager, whiteboard, Git hosting, automation, and QR services.
|
||||
icon: material/puzzle
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- services
|
||||
- integrations
|
||||
---
|
||||
|
||||
# Integrations
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Monitoring
|
||||
description: Prometheus metrics, Grafana dashboards, and Alertmanager for platform observability.
|
||||
icon: material/chart-timeline-variant
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- services
|
||||
- monitoring
|
||||
---
|
||||
|
||||
# Monitoring
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Tunnel (Pangolin)
|
||||
description: Manage the Pangolin tunnel for exposing services to the internet without port forwarding.
|
||||
icon: material/tunnel
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- services
|
||||
- networking
|
||||
---
|
||||
|
||||
# Tunnel (Pangolin)
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: User Provisioning
|
||||
description: Automatic account creation and sync across integrated services.
|
||||
icon: material/account-sync
|
||||
status: new
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- services
|
||||
---
|
||||
|
||||
# User Provisioning
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
title: Platform Settings
|
||||
description: Multi-tab admin settings for organization branding, theme colors, email configuration, feature toggles, and notification controls.
|
||||
icon: material/cog
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- configuration
|
||||
---
|
||||
|
||||
# Platform Settings
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
title: Documentation
|
||||
description: MkDocs site management, page analytics, comment moderation, and documentation settings.
|
||||
icon: material/book-open-variant
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- content
|
||||
---
|
||||
|
||||
# Documentation
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
title: Public Homepage
|
||||
description: Dynamic landing page that aggregates campaigns, shifts, media, events, and platform stats for public visitors.
|
||||
icon: material/home-variant
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- content
|
||||
---
|
||||
|
||||
# Public Homepage
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
title: Web Content
|
||||
description: Landing pages, homepage configuration, navigation, and documentation management.
|
||||
icon: material/web
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- content
|
||||
---
|
||||
|
||||
# Web Content
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Landing Pages
|
||||
description: Build campaign microsites with a drag-and-drop GrapesJS visual editor.
|
||||
icon: material/application-edit
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- content
|
||||
- landing-pages
|
||||
---
|
||||
|
||||
# Landing Pages
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
title: Navigation Settings
|
||||
description: Customize the public-facing navigation menu from the admin panel.
|
||||
icon: material/menu
|
||||
tags:
|
||||
- guide
|
||||
- admin
|
||||
- content
|
||||
---
|
||||
|
||||
# Navigation Settings
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
title: API Reference
|
||||
description: Complete REST API reference for both the Express API (port 4000) and Fastify Media API (port 4100).
|
||||
icon: material/api
|
||||
tags:
|
||||
- reference
|
||||
- developer
|
||||
- API
|
||||
---
|
||||
|
||||
# API Reference
|
||||
|
||||
@ -2,38 +2,213 @@
|
||||
title: Architecture
|
||||
description: System architecture, dual API design, database schema, and authentication flow.
|
||||
icon: material/sitemap
|
||||
tags:
|
||||
- reference
|
||||
- developer
|
||||
- architecture
|
||||
---
|
||||
|
||||
# Architecture
|
||||
|
||||
Changemaker Lite uses a dual-API architecture with a shared PostgreSQL database.
|
||||
Changemaker Lite uses a dual-API architecture with a shared PostgreSQL database, a React single-page application, and Nginx for subdomain routing across 30+ services.
|
||||
|
||||
!!! warning "Under Construction"
|
||||
Detailed architecture documentation is being written. Check back soon.
|
||||
---
|
||||
|
||||
## System Overview
|
||||
## System Diagram
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
Browser["Browser"] --> Nginx["Nginx<br/>(reverse proxy)"]
|
||||
Nginx --> Admin["React Admin GUI<br/>port 3000"]
|
||||
Nginx --> API["Express API<br/>port 4000"]
|
||||
Nginx --> MediaAPI["Fastify Media API<br/>port 4100"]
|
||||
Nginx --> MkDocs["MkDocs<br/>port 4003/4004"]
|
||||
Nginx --> Services["Other Services<br/>(Gitea, NocoDB, etc.)"]
|
||||
|
||||
API --> PostgreSQL[("PostgreSQL 16<br/>30+ tables")]
|
||||
MediaAPI --> PostgreSQL
|
||||
API --> Redis[("Redis<br/>cache + queues")]
|
||||
API --> BullMQ["BullMQ<br/>(email, video jobs)"]
|
||||
BullMQ --> Redis
|
||||
|
||||
subgraph Tunnel ["Public Access"]
|
||||
Newt["Newt Client"] --> Pangolin["Pangolin Server"]
|
||||
end
|
||||
Newt --> Nginx
|
||||
```
|
||||
Browser ──► Nginx (reverse proxy) ──┬──► Express API (port 4000) ──► PostgreSQL
|
||||
├──► Fastify Media API (port 4100) ──┘
|
||||
├──► React Admin GUI (port 3000)
|
||||
└──► MkDocs / Other Services
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Components
|
||||
|
||||
| Component | Technology | Role |
|
||||
|-----------|-----------|------|
|
||||
| Main API | Express.js + Prisma | Auth, campaigns, map, shifts, pages |
|
||||
| Media API | Fastify + Prisma | Video library, analytics, uploads |
|
||||
| Admin GUI | React + Ant Design | Single-page admin application |
|
||||
| Database | PostgreSQL 16 | Shared by both APIs (30+ tables) |
|
||||
| Cache | Redis | Rate limiting, job queues, geocoding |
|
||||
| Proxy | Nginx | Subdomain routing, security headers |
|
||||
| **Main API** | Express.js + TypeScript + Prisma | Auth, campaigns, map, shifts, pages, canvassing, email |
|
||||
| **Media API** | Fastify + TypeScript + Prisma | Video library, analytics, uploads, scheduling |
|
||||
| **Admin GUI** | React 19 + Vite + Ant Design + Zustand | Admin dashboard, public pages, volunteer portal, media gallery |
|
||||
| **Database** | PostgreSQL 16 | Shared by both APIs (30+ models via Prisma) |
|
||||
| **Cache** | Redis 7 | Rate limiting, BullMQ job queues, geocoding cache |
|
||||
| **Proxy** | Nginx | Subdomain routing, security headers, WebSocket upgrade |
|
||||
| **Tunnel** | Pangolin + Newt | Expose services without port forwarding |
|
||||
| **Monitoring** | Prometheus + Grafana + Alertmanager | Metrics collection, dashboards, alerting |
|
||||
|
||||
---
|
||||
|
||||
## Dual API Design
|
||||
|
||||
The platform runs two independent API servers sharing one PostgreSQL database:
|
||||
|
||||
=== "Express API (port 4000)"
|
||||
|
||||
The main API handles all core platform logic:
|
||||
|
||||
- **Authentication** — JWT access/refresh tokens, RBAC middleware
|
||||
- **Modules** — Influence (campaigns, responses), Map (locations, cuts, shifts, canvassing), Pages, Email Templates, Settings, Users, Payments, Social, Calendar
|
||||
- **Services** — Email queue (BullMQ), geocoding queue, Listmonk sync, Pangolin client, user provisioning
|
||||
- **ORM** — Prisma with 30+ models and migration history
|
||||
|
||||
=== "Fastify Media API (port 4100)"
|
||||
|
||||
A separate server optimized for media handling:
|
||||
|
||||
- **Video CRUD** — Upload with FFprobe metadata extraction
|
||||
- **Scheduled Publishing** — BullMQ queue with timezone support
|
||||
- **Analytics** — View tracking, watch time, completion rates (GDPR-compliant)
|
||||
- **Public Gallery** — Playlists, reactions, comments, SSE chat
|
||||
- **ORM** — Prisma (migrated from Drizzle, Feb 2026)
|
||||
|
||||
Both servers connect to the same database and share the same Prisma schema. This separation allows the media API to handle large file uploads and streaming independently from the main API's request/response cycle.
|
||||
|
||||
---
|
||||
|
||||
## Authentication Flow
|
||||
|
||||
- JWT access tokens (15 min) + refresh tokens (7 days)
|
||||
- Refresh token rotation with atomic database transaction
|
||||
- Role-based access control (5 roles)
|
||||
- Rate limiting on auth endpoints (10/min per IP)
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant API
|
||||
participant DB
|
||||
participant Redis
|
||||
|
||||
Client->>API: POST /api/auth/login {email, password}
|
||||
API->>Redis: Check rate limit (10/min per IP)
|
||||
Redis-->>API: OK
|
||||
API->>DB: Verify bcrypt password
|
||||
DB-->>API: User record
|
||||
API->>DB: Create refresh token
|
||||
API-->>Client: {accessToken (15min), refreshToken (7d)}
|
||||
|
||||
Note over Client: Authenticated requests
|
||||
Client->>API: GET /api/campaigns<br/>Authorization: Bearer <accessToken>
|
||||
API->>API: Verify JWT + check role (RBAC)
|
||||
API-->>Client: 200 OK
|
||||
|
||||
Note over Client: Token expired
|
||||
Client->>API: POST /api/auth/refresh {refreshToken}
|
||||
API->>DB: Atomic rotation (delete old, create new)
|
||||
API-->>Client: {new accessToken, new refreshToken}
|
||||
```
|
||||
|
||||
### Security Features
|
||||
|
||||
- **Password policy** — 12+ characters, uppercase, lowercase, digit (schema-enforced)
|
||||
- **Refresh token rotation** — Atomic Prisma transaction prevents race conditions
|
||||
- **User enumeration prevention** — Returns 401 (not 404) for missing users
|
||||
- **Rate limiting** — 10 requests/minute on auth endpoints via Redis
|
||||
- **11 roles** — `SUPER_ADMIN` (implicit bypass), 8 module-specific admin roles, `USER`, `TEMP`
|
||||
- **Encryption** — AES-256-GCM for sensitive DB fields (`ENCRYPTION_KEY` env var)
|
||||
|
||||
---
|
||||
|
||||
## Request Lifecycle
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A["Incoming Request"] --> B["Nginx"]
|
||||
B -->|"Host: api.domain"| C["Express API"]
|
||||
B -->|"Host: media.domain"| D["Fastify Media API"]
|
||||
B -->|"Host: app.domain"| E["React Admin GUI"]
|
||||
C --> F["Rate Limiter (Redis)"]
|
||||
F --> G["Auth Middleware (JWT)"]
|
||||
G --> H["Role Check (RBAC)"]
|
||||
H --> I["Validation (Zod)"]
|
||||
I --> J["Route Handler"]
|
||||
J --> K["Service Layer"]
|
||||
K --> L["Prisma ORM"]
|
||||
L --> M[("PostgreSQL")]
|
||||
J --> N["Response + Metrics"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Database Schema
|
||||
|
||||
The database contains **30+ Prisma models** organized by module:
|
||||
|
||||
| Module | Key Models |
|
||||
|--------|-----------|
|
||||
| **Auth** | `User`, `RefreshToken` |
|
||||
| **Influence** | `Campaign`, `CampaignEmail`, `CampaignResponse`, `Representative`, `PostalCode` |
|
||||
| **Map** | `Location`, `Address`, `Cut`, `Shift`, `ShiftSignup` |
|
||||
| **Canvass** | `CanvassSession`, `CanvassVisit`, `TrackingSession`, `TrackingPoint` |
|
||||
| **Pages** | `Page`, `PageBlock`, `EmailTemplate` |
|
||||
| **Media** | `Video`, `VideoReaction`, `VideoComment`, `VideoView`, `Playlist`, `PlaylistVideo` |
|
||||
| **Payments** | `StripeProduct`, `StripePrice`, `StripeDonationPage`, `StripeOrder` |
|
||||
| **Social** | `Friendship`, `SocialNotification`, `CalendarLayer`, `CalendarItem` |
|
||||
| **SMS** | `SmsContactList`, `SmsCampaign`, `SmsMessage`, `SmsConversation` |
|
||||
| **People** | `Contact`, `ContactAddress`, `ContactEmail`, `ContactPhone`, `ContactConnection` |
|
||||
| **Settings** | `SiteSettings`, `MapSettings` |
|
||||
|
||||
---
|
||||
|
||||
## Docker Compose Architecture
|
||||
|
||||
Services are organized into categories with dependency management:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Core ["Core (always started)"]
|
||||
PG["PostgreSQL"] --> API["Express API"]
|
||||
Redis --> API
|
||||
PG --> Media["Fastify Media API"]
|
||||
API --> Admin["React Admin"]
|
||||
Admin --> Nginx
|
||||
API --> Nginx
|
||||
Media --> Nginx
|
||||
end
|
||||
|
||||
subgraph Communication ["Communication (optional)"]
|
||||
RC["Rocket.Chat"] --> MongoDB
|
||||
Jitsi["Jitsi Meet (4 containers)"]
|
||||
Gancio["Gancio Events"]
|
||||
end
|
||||
|
||||
subgraph Monitoring ["Monitoring (profile)"]
|
||||
Prometheus --> Grafana
|
||||
Prometheus --> Alertmanager
|
||||
cAdvisor --> Prometheus
|
||||
NodeExporter --> Prometheus
|
||||
end
|
||||
|
||||
subgraph Tunnel ["Tunnel"]
|
||||
Newt --> Nginx
|
||||
end
|
||||
```
|
||||
|
||||
Docker healthchecks ensure proper startup order: PostgreSQL and Redis must be healthy before the API starts. The API runs migrations and seeding automatically via its entrypoint script.
|
||||
|
||||
---
|
||||
|
||||
## Subdomain Routing
|
||||
|
||||
Nginx routes requests based on the `Host` header. All services run on the `changemaker-lite` Docker bridge network.
|
||||
|
||||
| Pattern | Target |
|
||||
|---------|--------|
|
||||
| `app.DOMAIN` | Admin GUI (admin + public + volunteer + gallery) |
|
||||
| `api.DOMAIN` | Express API |
|
||||
| `media.DOMAIN` | Fastify Media API |
|
||||
| `DOMAIN` (root) | MkDocs static site |
|
||||
| `*.DOMAIN` | 15+ additional service subdomains |
|
||||
|
||||
See [Services](../services/index.md) for the complete subdomain table.
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Deployment
|
||||
description: Deploy Changemaker Lite to production with Docker, SSL, backups, and monitoring.
|
||||
icon: material/docker
|
||||
tags:
|
||||
- guide
|
||||
- operator
|
||||
- deployment
|
||||
- docker
|
||||
---
|
||||
|
||||
# Deployment
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Control Panel (CCP)
|
||||
description: Multi-tenant management for provisioning and operating multiple Changemaker Lite instances.
|
||||
icon: material/console
|
||||
status: new
|
||||
tags:
|
||||
- guide
|
||||
- operator
|
||||
- multi-tenant
|
||||
---
|
||||
|
||||
# Changemaker Control Panel (CCP)
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Environment Variables
|
||||
description: Complete reference for every .env variable in Changemaker Lite.
|
||||
icon: material/file-cog
|
||||
tags:
|
||||
- reference
|
||||
- getting-started
|
||||
- operator
|
||||
- configuration
|
||||
---
|
||||
|
||||
# Environment Variables
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Features at a Glance
|
||||
description: A visual overview of every Changemaker Lite module.
|
||||
icon: material/star-shooting
|
||||
tags:
|
||||
- reference
|
||||
- getting-started
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Features at a Glance
|
||||
|
||||
@ -2,6 +2,12 @@
|
||||
title: First Steps
|
||||
description: Log in, explore the dashboard, and set up your first campaign and volunteer shift.
|
||||
icon: material/shoe-print
|
||||
tags:
|
||||
- tutorial
|
||||
- getting-started
|
||||
- admin
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# First Steps
|
||||
|
||||
@ -2,6 +2,12 @@
|
||||
title: Getting Started
|
||||
description: Install and configure Changemaker Lite from scratch.
|
||||
icon: material/rocket-launch
|
||||
tags:
|
||||
- guide
|
||||
- getting-started
|
||||
- operator
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Getting Started
|
||||
|
||||
@ -2,6 +2,13 @@
|
||||
title: Installation
|
||||
description: System requirements, configuration wizard walkthrough, and initial service startup.
|
||||
icon: material/download
|
||||
tags:
|
||||
- guide
|
||||
- getting-started
|
||||
- operator
|
||||
- docker
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Installation
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Services Overview
|
||||
description: Complete catalog of all Docker services, ports, and startup commands.
|
||||
icon: material/docker
|
||||
tags:
|
||||
- reference
|
||||
- getting-started
|
||||
- operator
|
||||
- docker
|
||||
---
|
||||
|
||||
# Services Overview
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
title: Updates & Upgrades
|
||||
description: Keep Changemaker Lite up to date via the admin GUI or command line.
|
||||
icon: material/update
|
||||
tags:
|
||||
- guide
|
||||
- operator
|
||||
- upgrades
|
||||
---
|
||||
|
||||
# Updates & Upgrades
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Documentation
|
||||
description: Changemaker Lite documentation hub — guides for users, admins, and operators.
|
||||
icon: material/book-open-variant
|
||||
tags:
|
||||
- guide
|
||||
- getting-started
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Documentation
|
||||
@ -82,7 +87,7 @@ Welcome to the Changemaker Lite documentation. Whether you're a campaign volunte
|
||||
|
||||
Prometheus metrics, Grafana dashboards, Alertmanager rules, and health checks.
|
||||
|
||||
[:octicons-arrow-right-24: Monitoring](../blog/index.md){ .md-button .md-button--secondary } *Coming soon*
|
||||
[:octicons-arrow-right-24: Monitoring](admin/services/monitoring.md)
|
||||
|
||||
</div>
|
||||
|
||||
@ -120,7 +125,7 @@ Welcome to the Changemaker Lite documentation. Whether you're a campaign volunte
|
||||
|
||||
Development setup, code style, git workflow, and pull request guidelines.
|
||||
|
||||
[:octicons-arrow-right-24: Contributing](../blog/index.md){ .md-button .md-button--secondary } *Coming soon*
|
||||
[:octicons-arrow-right-24: Contributing](https://gitea.bnkops.com/admin/changemaker.lite){ target="_blank" }
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
---
|
||||
title: Philosophy
|
||||
description: Why we build Changemaker Lite — and why your movement should own its infrastructure.
|
||||
tags:
|
||||
- concept
|
||||
- philosophy
|
||||
- FOSS
|
||||
---
|
||||
|
||||
# Philosophy
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Services
|
||||
description: All services that make up the Changemaker Lite platform — configuration, ports, and links to upstream docs.
|
||||
icon: material/server-network
|
||||
tags:
|
||||
- reference
|
||||
- operator
|
||||
- services
|
||||
- docker
|
||||
---
|
||||
|
||||
# Services
|
||||
|
||||
@ -2,14 +2,18 @@
|
||||
title: Troubleshooting
|
||||
description: Solutions for common errors, CORS issues, database problems, and tunnel debugging.
|
||||
icon: material/bug
|
||||
tags:
|
||||
- troubleshooting
|
||||
- operator
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
Common issues and their solutions when running Changemaker Lite.
|
||||
|
||||
!!! warning "Under Construction"
|
||||
This troubleshooting guide is being expanded. Check back soon for more solutions.
|
||||
---
|
||||
|
||||
## CORS Errors in Production
|
||||
|
||||
@ -23,26 +27,202 @@ CORS_ORIGINS=https://app.yourdomain.org,http://localhost:3000
|
||||
|
||||
Then restart the API: `docker compose restart api`
|
||||
|
||||
## Pangolin Tunnel 403/302 Errors
|
||||
---
|
||||
|
||||
**Symptom:** All API endpoints return 302 redirects to Pangolin auth page.
|
||||
## Pangolin Tunnel — 403/302 Errors
|
||||
|
||||
**Fix:** In the Pangolin dashboard, set each resource to "Not Protected" (public access).
|
||||
**Symptom:** All API endpoints return 302 redirects to Pangolin auth page, or 403 Forbidden.
|
||||
|
||||
**Fix:** In the Pangolin dashboard, set each resource to **Not Protected** (public access). Critical resources to fix first:
|
||||
|
||||
1. `api.yourdomain.org` — Main API
|
||||
2. `app.yourdomain.org` — Admin GUI + public pages
|
||||
3. `media.yourdomain.org` — Media API
|
||||
|
||||
**Verify:**
|
||||
|
||||
```bash
|
||||
# Should return JSON, NOT a 302 redirect
|
||||
curl -I https://api.yourdomain.org/api/health
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Database Connection Failures
|
||||
|
||||
**Symptom:** API logs show database connection errors.
|
||||
|
||||
**Checklist:**
|
||||
|
||||
1. Check PostgreSQL: `docker compose ps v2-postgres`
|
||||
2. Verify `DATABASE_URL` in `.env`
|
||||
2. Verify `DATABASE_URL` in `.env` matches container name and port
|
||||
3. View logs: `docker compose logs v2-postgres --tail 50`
|
||||
4. Test connection: `docker compose exec api npx prisma db execute --stdin <<< "SELECT 1"`
|
||||
|
||||
---
|
||||
|
||||
## Redis Connection Failures
|
||||
|
||||
**Symptom:** API logs show Redis connection errors, rate limiting doesn't work.
|
||||
|
||||
**Checklist:**
|
||||
|
||||
1. Check Redis: `docker compose ps redis-changemaker`
|
||||
2. Verify `REDIS_PASSWORD` and `REDIS_URL` format in `.env`
|
||||
3. Test: `docker compose exec redis-changemaker redis-cli -a $REDIS_PASSWORD ping`
|
||||
2. Verify `REDIS_PASSWORD` and `REDIS_URL` format (`redis://:password@host:port`)
|
||||
3. View logs: `docker compose logs redis-changemaker --tail 50`
|
||||
4. Test: `docker compose exec redis-changemaker redis-cli -a $REDIS_PASSWORD ping`
|
||||
|
||||
---
|
||||
|
||||
## API Not Starting
|
||||
|
||||
**Symptom:** API container keeps restarting or won't start.
|
||||
|
||||
**Checklist:**
|
||||
|
||||
1. Check logs: `docker compose logs api --tail 100`
|
||||
2. Verify all required env vars are set (see `.env.example`)
|
||||
3. Run migrations: `docker compose exec api npx prisma migrate deploy`
|
||||
2. Verify all required env vars are set (compare with `.env.example`)
|
||||
3. Check database is ready: `docker compose ps v2-postgres` (should show "healthy")
|
||||
4. Run migrations manually: `docker compose exec api npx prisma migrate deploy`
|
||||
5. Check for port conflicts: `ss -tlnp | grep 4000`
|
||||
|
||||
---
|
||||
|
||||
## Containers in Restart Loops
|
||||
|
||||
**Symptom:** `docker compose ps` shows containers with "restarting" status.
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
```bash
|
||||
# Find restarting containers
|
||||
docker compose ps | grep -i restarting
|
||||
|
||||
# Check recent logs for the problem container
|
||||
docker compose logs <service-name> --tail 50
|
||||
|
||||
# Check container exit code
|
||||
docker inspect <container-name> --format='{{.State.ExitCode}}'
|
||||
```
|
||||
|
||||
**Common causes:**
|
||||
|
||||
- Missing environment variables (check `.env`)
|
||||
- Database not ready (healthcheck dependencies)
|
||||
- Port already in use by another process
|
||||
- Insufficient memory (check with `free -h`)
|
||||
|
||||
---
|
||||
|
||||
## Newt Tunnel Won't Connect
|
||||
|
||||
**Checklist (in order):**
|
||||
|
||||
1. **Credentials:** Verify `PANGOLIN_NEWT_ID` and `PANGOLIN_NEWT_SECRET` in `.env`
|
||||
2. **Endpoint:** Confirm `PANGOLIN_ENDPOINT` matches your Pangolin server URL
|
||||
3. **Logs:** `docker compose logs newt --tail 50`
|
||||
4. **Nginx running:** Newt depends on nginx — `docker compose ps nginx`
|
||||
5. **Network:** Ensure outbound HTTPS is not blocked by your firewall
|
||||
|
||||
---
|
||||
|
||||
## Migration Errors
|
||||
|
||||
**Symptom:** `prisma migrate deploy` fails.
|
||||
|
||||
**Common fixes:**
|
||||
|
||||
```bash
|
||||
# Check migration status
|
||||
docker compose exec api npx prisma migrate status
|
||||
|
||||
# If migrations are out of sync, reset (DESTRUCTIVE — dev only)
|
||||
docker compose exec api npx prisma migrate reset
|
||||
|
||||
# If shadow database errors, create one
|
||||
docker compose exec -T v2-postgres createdb -U changemaker prisma_shadow_diff
|
||||
```
|
||||
|
||||
!!! danger "Never use `prisma db push` in production"
|
||||
Always use `prisma migrate dev` (development) or `prisma migrate deploy` (production) to keep migration history in sync.
|
||||
|
||||
---
|
||||
|
||||
## Media API Upload Failures
|
||||
|
||||
**Symptom:** Video uploads fail with permission errors or 500 status.
|
||||
|
||||
**Checklist:**
|
||||
|
||||
1. Verify inbox volume is writable: check `media/local/inbox` has `:rw` mount
|
||||
2. Check disk space: `df -h`
|
||||
3. Verify FFmpeg is installed in container: `docker compose exec media-api ffprobe -version`
|
||||
4. Check upload size limit: default is 10 GB in Fastify multipart config
|
||||
|
||||
---
|
||||
|
||||
## Email Not Sending
|
||||
|
||||
**Symptom:** Advocacy emails or notifications aren't delivered.
|
||||
|
||||
**Checklist:**
|
||||
|
||||
1. Check `EMAIL_TEST_MODE` — if `true`, all emails go to MailHog (`http://localhost:8025`)
|
||||
2. Verify SMTP credentials in `.env` (`SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`)
|
||||
3. Check BullMQ queue: visit **Admin > Email Queue** or check logs
|
||||
4. Test SMTP from Settings: **Admin > Settings > Email > Test Connection**
|
||||
|
||||
---
|
||||
|
||||
## Services Unreachable via Tunnel
|
||||
|
||||
**Checklist:**
|
||||
|
||||
1. Verify nginx is running: `docker compose ps nginx`
|
||||
2. Test locally first: `curl http://localhost:4000/api/health`
|
||||
3. Check nginx logs: `docker compose logs nginx --tail 50`
|
||||
4. Verify DNS: `dig app.yourdomain.org` should point to your Pangolin server
|
||||
5. Check Pangolin resources are all set to "Not Protected"
|
||||
|
||||
---
|
||||
|
||||
## Slow Map Performance
|
||||
|
||||
**Symptom:** Map page is slow or returns 500 errors with many locations.
|
||||
|
||||
**Causes and fixes:**
|
||||
|
||||
- **Too many locations loaded at once** — the API limits by address count with debounced bounds queries
|
||||
- **Missing indexes** — verify database has the 5 performance indexes on Location/Address tables
|
||||
- **Browser memory** — marker clustering activates above zoom level 18; below that, addresses are grouped
|
||||
|
||||
---
|
||||
|
||||
## Docker Disk Space
|
||||
|
||||
**Symptom:** Builds fail, containers can't start, or images won't pull.
|
||||
|
||||
```bash
|
||||
# Check disk usage
|
||||
df -h
|
||||
|
||||
# Clean unused Docker resources
|
||||
docker system prune -f
|
||||
|
||||
# Clean old images (keep only last 2 days)
|
||||
docker image prune -a --filter "until=48h"
|
||||
|
||||
# Check what's using space
|
||||
docker system df
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Getting Help
|
||||
|
||||
If your issue isn't listed here:
|
||||
|
||||
1. Check the API logs: `docker compose logs api --tail 200`
|
||||
2. Search the [Gitea issues](https://gitea.bnkops.com/admin/changemaker.lite/issues)
|
||||
3. Review the [Deployment guide](../deployment/index.md) for production-specific issues
|
||||
4. File a new issue with your logs and `.env` (redact passwords)
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Campaigns
|
||||
description: Find advocacy campaigns, look up your representatives by postal code, and send emails.
|
||||
icon: material/email-fast
|
||||
tags:
|
||||
- guide
|
||||
- user
|
||||
- influence
|
||||
- campaigns
|
||||
---
|
||||
|
||||
# Campaigns
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Donations
|
||||
description: Support the cause with one-time donations on branded pages with goals and suggested amounts.
|
||||
icon: material/hand-heart
|
||||
status: new
|
||||
tags:
|
||||
- guide
|
||||
- user
|
||||
- payments
|
||||
---
|
||||
|
||||
# Donations
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
title: Events (Gancio)
|
||||
description: Self-hosted event management with automatic shift-to-event sync powered by Gancio.
|
||||
icon: material/calendar-star
|
||||
tags:
|
||||
- guide
|
||||
- user
|
||||
- events
|
||||
---
|
||||
|
||||
# Events (Gancio)
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Gallery
|
||||
description: Watch campaign videos, browse photos, explore playlists, and view shorts.
|
||||
icon: material/play-box-multiple
|
||||
tags:
|
||||
- guide
|
||||
- user
|
||||
- media
|
||||
- gallery
|
||||
---
|
||||
|
||||
# Gallery
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
title: User Guide
|
||||
description: How to use Changemaker Lite as a public visitor or registered supporter.
|
||||
icon: material/account
|
||||
tags:
|
||||
- guide
|
||||
- user
|
||||
---
|
||||
|
||||
# User Guide
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
title: Map
|
||||
description: Explore the interactive community map showing locations across your area.
|
||||
icon: material/map
|
||||
tags:
|
||||
- guide
|
||||
- user
|
||||
- map
|
||||
---
|
||||
|
||||
# Map
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Self-Service Contact Profile
|
||||
description: Token-based CRM contact profiles where supporters can view their engagement history, edit their details, manage communication preferences, and opt out.
|
||||
icon: material/card-account-details
|
||||
status: new
|
||||
tags:
|
||||
- guide
|
||||
- user
|
||||
- CRM
|
||||
---
|
||||
|
||||
# Self-Service Contact Profile
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
title: Shifts
|
||||
description: Sign up for volunteer shifts, join canvassing teams, and get started quickly with QR codes.
|
||||
icon: material/calendar-clock
|
||||
tags:
|
||||
- guide
|
||||
- user
|
||||
- shifts
|
||||
---
|
||||
|
||||
# Shifts
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Shop & Pricing
|
||||
description: Browse campaign merchandise and membership subscription plans.
|
||||
icon: material/shopping
|
||||
status: new
|
||||
tags:
|
||||
- guide
|
||||
- user
|
||||
- payments
|
||||
---
|
||||
|
||||
# Shop & Pricing
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Achievements & Leaderboard
|
||||
description: Badge-based gamification and volunteer leaderboards to recognize and encourage platform participation.
|
||||
icon: material/trophy
|
||||
status: new
|
||||
tags:
|
||||
- guide
|
||||
- volunteer
|
||||
- social
|
||||
---
|
||||
|
||||
# Achievements & Leaderboard
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Canvassing
|
||||
description: GPS-guided door-to-door canvassing with the volunteer map, visit recording, and walking routes.
|
||||
icon: material/map-marker-path
|
||||
tags:
|
||||
- guide
|
||||
- volunteer
|
||||
- map
|
||||
- canvassing
|
||||
---
|
||||
|
||||
# Canvassing
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
title: Volunteer Guide
|
||||
description: Getting started as a volunteer — shifts, canvassing, social connections, and achievements.
|
||||
icon: material/walk
|
||||
tags:
|
||||
- guide
|
||||
- volunteer
|
||||
---
|
||||
|
||||
# Volunteer Guide
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
title: Your Shifts
|
||||
description: View your assigned volunteer shifts, activity history, and canvassing stats.
|
||||
icon: material/calendar-check
|
||||
tags:
|
||||
- guide
|
||||
- volunteer
|
||||
- shifts
|
||||
---
|
||||
|
||||
# Your Shifts
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
title: Social Connections
|
||||
description: Connect with fellow volunteers through friend requests, activity feeds, groups, and real-time notifications.
|
||||
icon: material/account-heart
|
||||
status: new
|
||||
tags:
|
||||
- guide
|
||||
- volunteer
|
||||
- social
|
||||
---
|
||||
|
||||
# Social Connections
|
||||
|
||||
50
mkdocs/docs/includes/abbreviations.md
Normal file
@ -0,0 +1,50 @@
|
||||
*[API]: Application Programming Interface
|
||||
*[JWT]: JSON Web Token
|
||||
*[RBAC]: Role-Based Access Control
|
||||
*[CORS]: Cross-Origin Resource Sharing
|
||||
*[SMTP]: Simple Mail Transfer Protocol
|
||||
*[CSV]: Comma-Separated Values
|
||||
*[ORM]: Object-Relational Mapping
|
||||
*[SSL]: Secure Sockets Layer
|
||||
*[TLS]: Transport Layer Security
|
||||
*[DNS]: Domain Name System
|
||||
*[CRUD]: Create, Read, Update, Delete
|
||||
*[SSO]: Single Sign-On
|
||||
*[SPA]: Single Page Application
|
||||
*[CLI]: Command Line Interface
|
||||
*[GUI]: Graphical User Interface
|
||||
*[QR]: Quick Response
|
||||
*[GPS]: Global Positioning System
|
||||
*[GDPR]: General Data Protection Regulation
|
||||
*[CDN]: Content Delivery Network
|
||||
*[VPS]: Virtual Private Server
|
||||
*[CGNAT]: Carrier-Grade Network Address Translation
|
||||
*[NAR]: National Address Register
|
||||
*[CRM]: Customer Relationship Management
|
||||
*[OG]: Open Graph
|
||||
*[DDoS]: Distributed Denial of Service
|
||||
*[SSE]: Server-Sent Events
|
||||
*[UUID]: Universally Unique Identifier
|
||||
*[FOSS]: Free and Open Source Software
|
||||
*[HSTS]: HTTP Strict Transport Security
|
||||
*[CSP]: Content Security Policy
|
||||
*[BullMQ]: Bull Message Queue
|
||||
*[FFprobe]: FFmpeg Probe — media metadata extraction tool
|
||||
*[FFmpeg]: Fast Forward MPEG — multimedia processing framework
|
||||
*[XMPP]: Extensible Messaging and Presence Protocol
|
||||
*[ACME]: Automatic Certificate Management Environment
|
||||
*[SSH]: Secure Shell
|
||||
*[YAML]: YAML Ain't Markup Language
|
||||
*[JSON]: JavaScript Object Notation
|
||||
*[HTML]: HyperText Markup Language
|
||||
*[CSS]: Cascading Style Sheets
|
||||
*[UI]: User Interface
|
||||
*[UX]: User Experience
|
||||
*[CI/CD]: Continuous Integration / Continuous Deployment
|
||||
*[UID]: User Identifier
|
||||
*[GID]: Group Identifier
|
||||
*[RSVP]: Respondez S'il Vous Plait
|
||||
*[CTR]: Click-Through Rate
|
||||
*[KPI]: Key Performance Indicator
|
||||
*[AES]: Advanced Encryption Standard
|
||||
*[PIN]: Personal Identification Number
|
||||
@ -1,7 +0,0 @@
|
||||
---
|
||||
template: lander.html
|
||||
hide:
|
||||
- navigation
|
||||
- toc
|
||||
title: "lander"
|
||||
---
|
||||
@ -1,7 +0,0 @@
|
||||
---
|
||||
template: main.html
|
||||
hide:
|
||||
- navigation
|
||||
- toc
|
||||
title: "main"
|
||||
---
|
||||
@ -1,7 +0,0 @@
|
||||
{% extends "main.html" %}
|
||||
{% block content %}
|
||||
<style>
|
||||
* { box-sizing: border-box; } body {margin: 0;}#i25w{padding:10px;}
|
||||
</style>
|
||||
<body id="i7af"><div id="i25w">Insert your text here</div></body>
|
||||
{% endblock %}
|
||||
@ -1,7 +0,0 @@
|
||||
---
|
||||
template: test-page.html
|
||||
hide:
|
||||
- navigation
|
||||
- toc
|
||||
title: "Test Page"
|
||||
---
|
||||
@ -1,461 +0,0 @@
|
||||
# Test
|
||||
|
||||
Testing page.
|
||||
|
||||
[[test-page]]
|
||||
|
||||
TEst
|
||||
|
||||
test
|
||||
|
||||
test
|
||||
|
||||
<div class="photo-block" data-photo-id="1" data-size="large" data-caption="" data-link-to-gallery="true" data-alignment="center">Loading...</div>
|
||||
|
||||
|
||||
<div style="text-align: center; padding: 40px 20px; background: linear-gradient(135deg, #1a1a2e, #16213e); border-radius: 12px; margin: 16px 0;">
|
||||
<h2 style="color: #fff; margin: 12px 0;">Choose Your Plan</h2>
|
||||
<p style="color: rgba(255,255,255,0.8); margin-bottom: 24px;">Get access to exclusive content and features.</p>
|
||||
<a href="http://app.org/pricing" style="display: inline-block; padding: 14px 36px; background: #722ed1; color: #fff; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 1.1rem;">View Plans</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="photo-card-block" data-photo-id="1" data-photo-title="vlcsnap-2026-01-09-15h39m52s898.png" data-photo-format="png" data-photo-width="1920" data-photo-height="1040" data-photo-views="0" style="max-width: 480px; margin: 0 auto;">
|
||||
<a href="http://app.org/gallery?expanded=photo-1" style="display: block; text-decoration: none; color: inherit; border-radius: 12px; overflow: hidden; background: #1b2838; box-shadow: 0 4px 12px rgba(0,0,0,0.3);">
|
||||
<div style="position: relative; padding-bottom: 66.67%; background: #0d1b2a; overflow: hidden;">
|
||||
<img src="http://app.orgdata:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22480%22%20height%3D%22320%22%20viewBox%3D%220%200%20480%20320%22%3E%3Crect%20fill%3D%22%230d1b2a%22%20width%3D%22480%22%20height%3D%22320%22%2F%3E%3Ccircle%20cx%3D%22240%22%20cy%3D%22160%22%20r%3D%2232%22%20fill%3D%22rgba(46%2C125%2C50%2C0.6)%22%2F%3E%3Crect%20x%3D%22224%22%20y%3D%22144%22%20width%3D%2232%22%20height%3D%2232%22%20rx%3D%224%22%20fill%3D%22none%22%20stroke%3D%22%23fff%22%20stroke-width%3D%222%22%2F%3E%3Ccircle%20cx%3D%22234%22%20cy%3D%22154%22%20r%3D%223%22%20fill%3D%22%23fff%22%2F%3E%3Cpath%20d%3D%22M256%20176l-10-10L224%20176%22%20fill%3D%22none%22%20stroke%3D%22%23fff%22%20stroke-width%3D%222%22%2F%3E%3C%2Fsvg%3E" alt="vlcsnap-2026-01-09-15h39m52s898.png" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover;" />
|
||||
<span style="position: absolute; top: 8px; left: 8px; background: #2e7d32; color: #fff; font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 4px;">PNG</span>
|
||||
<span style="position: absolute; bottom: 8px; right: 8px; background: rgba(0,0,0,0.8); color: #fff; font-size: 12px; font-weight: 500; padding: 2px 6px; border-radius: 4px;">1920×1040</span>
|
||||
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 56px; height: 56px; background: rgba(0,0,0,0.5); border-radius: 50%; display: flex; align-items: center; justify-content: center;">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding: 12px 16px;">
|
||||
<div style="color: #fff; font-size: 15px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">vlcsnap-2026-01-09-15h39m52s898.png</div>
|
||||
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 6px;">
|
||||
<span style="color: #8899aa; font-size: 13px;">0 views</span>
|
||||
<span style="color: #43cea2; font-size: 13px; font-weight: 500;">View →</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="cm-product-mlrk53ro" style="text-align:center;padding:32px 20px;background:linear-gradient(135deg,#1a1a2e,#16213e);border-radius:12px;margin:16px 0;max-width:420px;margin-left:auto;margin-right:auto;">
|
||||
<div style="width:80px;height:80px;border-radius:12px;background:linear-gradient(135deg,#9d4edd,#722ed1);display:flex;align-items:center;justify-content:center;margin:0 auto 16px;"><span style="font-size:36px;color:#fff;">🛒</span></div>
|
||||
<div style="display:inline-block;padding:2px 10px;border-radius:4px;background:#1890ff;color:#fff;font-size:11px;font-weight:600;margin-bottom:8px;">DIGITAL</div>
|
||||
<h3 style="color:#fff;margin:8px 0 4px;">Test Product 1</h3>
|
||||
<p style="color:rgba(255,255,255,0.7);font-size:0.9rem;margin-bottom:12px;">A test product</p>
|
||||
<p style="color:#fff;font-size:1.4rem;font-weight:700;margin-bottom:20px;">$90.00</p>
|
||||
<div id="cm-product-mlrk53ro-form" style="max-width:320px;margin:0 auto;text-align:left;">
|
||||
<input type="email" id="cm-product-mlrk53ro-email" placeholder="your@email.com *" style="width:100%;padding:10px 14px;background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.25);border-radius:8px;color:#fff;font-size:0.95rem;box-sizing:border-box;margin-bottom:10px;outline:none;" required />
|
||||
<input type="text" id="cm-product-mlrk53ro-name" placeholder="Name (optional)" style="width:100%;padding:10px 14px;background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.25);border-radius:8px;color:#fff;font-size:0.95rem;box-sizing:border-box;margin-bottom:10px;outline:none;" />
|
||||
<div id="cm-product-mlrk53ro-error" style="color:#ff4d4f;font-size:0.9rem;margin-bottom:8px;display:none;"></div>
|
||||
<button type="button" id="cm-product-mlrk53ro-submit" style="width:100%;padding:14px 24px;background:#722ed1;color:#fff;border:none;border-radius:8px;font-weight:600;font-size:1.05rem;cursor:pointer;">Buy Now — $90.00</button>
|
||||
<p style="margin-top:12px;font-size:0.75rem;color:rgba(255,255,255,0.4);text-align:center;">
|
||||
Secure payment via Stripe. <a href="http://app.org/shop" style="color:rgba(255,255,255,0.5);">Browse all products</a>
|
||||
</p>
|
||||
</div>
|
||||
<noscript><a href="http://app.org/shop" style="display:inline-block;padding:14px 36px;background:#722ed1;color:#fff;text-decoration:none;border-radius:8px;font-weight:600;">View in Shop</a></noscript>
|
||||
</div>
|
||||
<script>
|
||||
(function(){
|
||||
var root=document.getElementById('cm-product-mlrk53ro');
|
||||
if(!root)return;
|
||||
var apiUrl='http://api.org';
|
||||
var productId='cmlritfdm0000mkbgf9eelg4t';
|
||||
var submitBtn=document.getElementById('cm-product-mlrk53ro-submit');
|
||||
var emailInput=document.getElementById('cm-product-mlrk53ro-email');
|
||||
var nameInput=document.getElementById('cm-product-mlrk53ro-name');
|
||||
var errDiv=document.getElementById('cm-product-mlrk53ro-error');
|
||||
function showErr(msg){errDiv.textContent=msg;errDiv.style.display='block';}
|
||||
function hideErr(){errDiv.style.display='none';}
|
||||
submitBtn.addEventListener('click',function(){
|
||||
hideErr();
|
||||
var email=(emailInput.value||'').trim();
|
||||
if(!email||email.indexOf('@')<1){showErr('Please enter a valid email address.');return;}
|
||||
submitBtn.disabled=true;submitBtn.textContent='Processing...';
|
||||
fetch(apiUrl+'/api/payments/purchase',{
|
||||
method:'POST',headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({productId:productId,buyerEmail:email,buyerName:(nameInput.value||'').trim()||undefined})
|
||||
}).then(function(r){return r.json();}).then(function(data){
|
||||
if(data.url){window.location.href=data.url;}
|
||||
else{showErr(data.error&&data.error.message||'Something went wrong.');submitBtn.disabled=false;submitBtn.textContent='Buy Now';}
|
||||
}).catch(function(){showErr('Connection error. Please try again.');submitBtn.disabled=false;submitBtn.textContent='Buy Now';});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<div id="cm-donate-mlrjofs1" style="text-align:center;padding:40px 20px;background:linear-gradient(135deg,#2d1b69,#1a1a2e);border-radius:12px;margin:16px 0;max-width:560px;margin-left:auto;margin-right:auto;">
|
||||
<p style="font-size:48px;margin:0;">❤️</p>
|
||||
<h2 style="color:#fff;margin:12px 0;">Support Our Work</h2>
|
||||
<p style="color:rgba(255,255,255,0.8);margin-bottom:24px;">Every contribution makes a difference. Choose an amount below.</p>
|
||||
|
||||
<div id="cm-donate-mlrjofs1-amounts" style="margin-bottom: 16px;">
|
||||
<button type="button" class="cm-donate-mlrjofs1-amt" data-cents="1000" style="display:inline-block;padding:10px 22px;margin:4px;color:#fff;text-decoration:none;border-radius:8px;font-weight:600;font-size:1rem;cursor:pointer;border:2px solid rgba(255,255,255,0.25);background:rgba(255,255,255,0.08);">$10</button>
|
||||
<button type="button" class="cm-donate-mlrjofs1-amt" data-cents="2500" style="display:inline-block;padding:10px 22px;margin:4px;color:#fff;text-decoration:none;border-radius:8px;font-weight:600;font-size:1rem;cursor:pointer;border:2px solid rgba(255,255,255,0.25);background:rgba(255,255,255,0.08);">$25</button>
|
||||
<button type="button" class="cm-donate-mlrjofs1-amt" data-cents="5000" style="display:inline-block;padding:10px 22px;margin:4px;color:#fff;text-decoration:none;border-radius:8px;font-weight:600;font-size:1rem;cursor:pointer;border:2px solid rgba(255,255,255,0.25);background:rgba(255,255,255,0.08);">$50</button>
|
||||
<button type="button" class="cm-donate-mlrjofs1-amt" data-cents="10000" style="display:inline-block;padding:10px 22px;margin:4px;color:#fff;text-decoration:none;border-radius:8px;font-weight:600;font-size:1rem;cursor:pointer;border:2px solid rgba(255,255,255,0.25);background:rgba(255,255,255,0.08);">$100</button>
|
||||
<button type="button" class="cm-donate-mlrjofs1-amt" data-cents="custom" style="display:inline-block;padding:10px 22px;margin:4px;color:#fff;text-decoration:none;border-radius:8px;font-weight:600;font-size:1rem;cursor:pointer;border:2px solid rgba(255,255,255,0.25);background:rgba(255,255,255,0.08);">Custom</button>
|
||||
</div>
|
||||
<div id="cm-donate-mlrjofs1-custom" style="display:none;margin-bottom:12px;">
|
||||
<input type="number" id="cm-donate-mlrjofs1-custom-input" min="1" step="1" placeholder="Enter amount ($)" style="width:100%;padding:10px 14px;background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.25);border-radius:8px;color:#fff;font-size:0.95rem;box-sizing:border-box;margin-bottom:10px;outline:none;" />
|
||||
</div>
|
||||
<div id="cm-donate-mlrjofs1-form" style="display:none;max-width:360px;margin:0 auto;text-align:left;">
|
||||
<input type="email" id="cm-donate-mlrjofs1-email" placeholder="your@email.com *" style="width:100%;padding:10px 14px;background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.25);border-radius:8px;color:#fff;font-size:0.95rem;box-sizing:border-box;margin-bottom:10px;outline:none;" required />
|
||||
<input type="text" id="cm-donate-mlrjofs1-name" placeholder="Name (optional)" style="width:100%;padding:10px 14px;background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.25);border-radius:8px;color:#fff;font-size:0.95rem;box-sizing:border-box;margin-bottom:10px;outline:none;" />
|
||||
<div style="margin-bottom:12px;color:rgba(255,255,255,0.7);font-size:0.85rem;">
|
||||
<label><input type="checkbox" id="cm-donate-mlrjofs1-anon" style="margin-right:6px;" />Make my donation anonymous</label>
|
||||
</div>
|
||||
<div id="cm-donate-mlrjofs1-error" style="color:#ff4d4f;font-size:0.9rem;margin-bottom:8px;display:none;"></div>
|
||||
<button type="button" id="cm-donate-mlrjofs1-submit" style="width:100%;padding:14px 24px;background:#eb2f96;color:#fff;border:none;border-radius:8px;font-weight:600;font-size:1.05rem;cursor:pointer;">
|
||||
Donate
|
||||
</button>
|
||||
<p style="margin-top:12px;font-size:0.75rem;color:rgba(255,255,255,0.4);text-align:center;">
|
||||
Secure payment via Stripe. <a href="http://app.org/donate" style="color:rgba(255,255,255,0.5);">Open full donate page</a>
|
||||
</p>
|
||||
</div>
|
||||
<noscript><a href="http://app.org/donate" style="display:inline-block;padding:14px 36px;background:#eb2f96;color:#fff;text-decoration:none;border-radius:8px;font-weight:600;">Donate Now</a></noscript>
|
||||
</div>
|
||||
<script>
|
||||
(function(){
|
||||
var root=document.getElementById('cm-donate-mlrjofs1');
|
||||
if(!root)return;
|
||||
var apiUrl='http://api.org';
|
||||
var selectedCents=0;
|
||||
var form=document.getElementById('cm-donate-mlrjofs1-form');
|
||||
var submitBtn=document.getElementById('cm-donate-mlrjofs1-submit');
|
||||
var emailInput=document.getElementById('cm-donate-mlrjofs1-email');
|
||||
var nameInput=document.getElementById('cm-donate-mlrjofs1-name');
|
||||
var anonBox=document.getElementById('cm-donate-mlrjofs1-anon');
|
||||
var errDiv=document.getElementById('cm-donate-mlrjofs1-error');
|
||||
var amtBtns=root.querySelectorAll('.cm-donate-mlrjofs1-amt');
|
||||
var customWrap=document.getElementById('cm-donate-mlrjofs1-custom');
|
||||
var customInput=document.getElementById('cm-donate-mlrjofs1-custom-input');
|
||||
var activeStyle='background:#eb2f96;border-color:#eb2f96;';
|
||||
var baseStyle='background:rgba(255,255,255,0.08);border-color:rgba(255,255,255,0.25);';
|
||||
function selectAmt(btn,cents){
|
||||
amtBtns.forEach(function(b){b.style.cssText='display:inline-block;padding:10px 22px;margin:4px;color:#fff;text-decoration:none;border-radius:8px;font-weight:600;font-size:1rem;cursor:pointer;border:2px solid rgba(255,255,255,0.25);background:rgba(255,255,255,0.08);'+baseStyle;});
|
||||
btn.style.cssText='display:inline-block;padding:10px 22px;margin:4px;color:#fff;text-decoration:none;border-radius:8px;font-weight:600;font-size:1rem;cursor:pointer;border:2px solid rgba(255,255,255,0.25);background:rgba(255,255,255,0.08);'+activeStyle;
|
||||
if(cents==='custom'){
|
||||
selectedCents=0;customWrap.style.display='block';
|
||||
customInput.focus();
|
||||
submitBtn.textContent='Donate';
|
||||
}else{
|
||||
selectedCents=parseInt(cents,10);customWrap.style.display='none';
|
||||
submitBtn.textContent='Donate $'+(selectedCents/100).toFixed(0);
|
||||
}
|
||||
form.style.display='block';
|
||||
}
|
||||
amtBtns.forEach(function(b){
|
||||
b.addEventListener('click',function(){selectAmt(b,b.getAttribute('data-cents'));});
|
||||
});
|
||||
if(customInput){customInput.addEventListener('input',function(){
|
||||
var v=parseFloat(customInput.value);
|
||||
if(v>0){selectedCents=Math.round(v*100);submitBtn.textContent='Donate $'+v.toFixed(2);}
|
||||
else{selectedCents=0;submitBtn.textContent='Donate';}
|
||||
});}
|
||||
function showErr(msg){errDiv.textContent=msg;errDiv.style.display='block';}
|
||||
function hideErr(){errDiv.style.display='none';}
|
||||
submitBtn.addEventListener('click',function(){
|
||||
hideErr();
|
||||
var email=(emailInput.value||'').trim();
|
||||
if(!email||email.indexOf('@')<1){showErr('Please enter a valid email address.');return;}
|
||||
if(!selectedCents||selectedCents<100){showErr('Please select a donation amount.');return;}
|
||||
submitBtn.disabled=true;submitBtn.textContent='Processing...';
|
||||
fetch(apiUrl+'/api/payments/donate',{
|
||||
method:'POST',headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({amountCents:selectedCents,email:email,name:(nameInput.value||'').trim()||undefined,isAnonymous:!!(anonBox&&anonBox.checked)})
|
||||
}).then(function(r){return r.json();}).then(function(data){
|
||||
if(data.url){window.location.href=data.url;}
|
||||
else{showErr(data.error&&data.error.message||'Something went wrong.');submitBtn.disabled=false;submitBtn.textContent='Donate';}
|
||||
}).catch(function(){showErr('Connection error. Please try again.');submitBtn.disabled=false;submitBtn.textContent='Donate';});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
<div style="text-align: center; padding: 40px 20px; background: linear-gradient(135deg, #2d1b69, #1a1a2e); border-radius: 12px; margin: 16px 0; max-width: 560px; margin-left: auto; margin-right: auto;">
|
||||
<p style="font-size: 48px; margin: 0;">❤️</p>
|
||||
<h2 style="color: #fff; margin: 12px 0;">Support Our Work</h2>
|
||||
<p style="color: rgba(255,255,255,0.8); margin-bottom: 24px;">Every contribution makes a difference. Choose an amount below.</p>
|
||||
<div style="margin-bottom: 16px;">
|
||||
<a href="http://app.org/donate?amount=1000" style="display: inline-block; padding: 10px 22px; margin: 4px; background: rgba(255,255,255,0.12); color: #fff; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 1rem; border: 2px solid rgba(255,255,255,0.2);">$10</a>
|
||||
<a href="http://app.org/donate?amount=2500" style="display: inline-block; padding: 10px 22px; margin: 4px; background: rgba(255,255,255,0.12); color: #fff; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 1rem; border: 2px solid rgba(255,255,255,0.2);">$25</a>
|
||||
<a href="http://app.org/donate?amount=5000" style="display: inline-block; padding: 10px 22px; margin: 4px; background: rgba(255,255,255,0.12); color: #fff; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 1rem; border: 2px solid rgba(255,255,255,0.2);">$50</a>
|
||||
<a href="http://app.org/donate?amount=10000" style="display: inline-block; padding: 10px 22px; margin: 4px; background: rgba(255,255,255,0.12); color: #fff; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 1rem; border: 2px solid rgba(255,255,255,0.2);">$100</a>
|
||||
</div>
|
||||
<a href="http://app.org/donate" style="display: inline-block; padding: 10px 22px; margin: 4px; background: #eb2f96; color: #fff; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 1rem;">Custom Amount</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="text-align: center; padding: 40px 20px; background: linear-gradient(135deg, #9d4edd, #722ed1); border-radius: 12px; margin: 16px 0;">
|
||||
<p style="font-size: 48px; margin: 0;">🛒</p>
|
||||
<h2 style="color: #fff; margin: 12px 0;">Browse Our Products</h2>
|
||||
<p style="color: rgba(255,255,255,0.8); margin-bottom: 24px;">Reports, toolkits, event tickets, and more.</p>
|
||||
<a href="http://app.org/shop" style="display: inline-block; padding: 14px 36px; background: #fff; color: #722ed1; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 1.1rem;">Shop Now</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="text-align: center; padding: 40px 20px; background: linear-gradient(135deg, #1a1a2e, #16213e); border-radius: 12px; margin: 16px 0;">
|
||||
<h2 style="color: #fff; margin: 12px 0;">Choose Your Plan</h2>
|
||||
<p style="color: rgba(255,255,255,0.8); margin-bottom: 24px;">Get access to exclusive content and features.</p>
|
||||
<a href="http://app.org/pricing" style="display: inline-block; padding: 14px 36px; background: #722ed1; color: #fff; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 1.1rem;">View Plans</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="text-align: center; padding: 40px 20px; background: linear-gradient(135deg, #2d1b69, #1a1a2e); border-radius: 12px; margin: 16px 0;">
|
||||
<p style="font-size: 48px; margin: 0;">❤️</p>
|
||||
<h2 style="color: #fff; margin: 12px 0;">Support Our Cause</h2>
|
||||
<p style="color: rgba(255,255,255,0.8); margin-bottom: 24px;">Your contribution helps us create lasting change in our community.</p>
|
||||
<a href="http://app.org/donate" style="display: inline-block; padding: 14px 36px; background: #eb2f96; color: #fff; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 1.1rem;">Donate Now</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="video-card-block" data-video-id="2" data-video-title="Testing This Sucker" data-video-duration="594" data-video-quality="" data-video-views="0" style="max-width: 480px; margin: 0 auto;">
|
||||
<a href="http://app.org/gallery/watch/2" style="display: block; text-decoration: none; color: inherit; border-radius: 12px; overflow: hidden; background: #1b2838; box-shadow: 0 4px 12px rgba(0,0,0,0.3);">
|
||||
<div style="position: relative; padding-bottom: 56.25%; background: #0d1b2a; overflow: hidden;">
|
||||
<img src="http://app.orgdata:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22480%22%20height%3D%22270%22%20viewBox%3D%220%200%20480%20270%22%3E%3Crect%20fill%3D%22%230d1b2a%22%20width%3D%22480%22%20height%3D%22270%22%2F%3E%3Ccircle%20cx%3D%22240%22%20cy%3D%22135%22%20r%3D%2232%22%20fill%3D%22rgba(157%2C78%2C221%2C0.6)%22%2F%3E%3Cpolygon%20points%3D%22230%2C118%20258%2C135%20230%2C152%22%20fill%3D%22%23fff%22%2F%3E%3C%2Fsvg%3E" alt="Testing This Sucker" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover;" />
|
||||
|
||||
<span style="position: absolute; bottom: 8px; right: 8px; background: rgba(0,0,0,0.8); color: #fff; font-size: 12px; font-weight: 500; padding: 2px 6px; border-radius: 4px;">9:54</span>
|
||||
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 56px; height: 56px; background: rgba(0,0,0,0.5); border-radius: 50%; display: flex; align-items: center; justify-content: center;">
|
||||
<svg width="24" height="24" viewBox="0 0 20 20" fill="#fff"><path d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding: 12px 16px;">
|
||||
<div style="color: #fff; font-size: 15px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">Testing This Sucker</div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 6px;">
|
||||
<span style="color: #8899aa; font-size: 13px;">0 views</span>
|
||||
<span style="color: #9d4edd; font-size: 13px; font-weight: 500;">Watch →</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
# Test
|
||||
|
||||
Testing page.
|
||||
|
||||
|
||||
<div class="photo-block" data-photo-id="1" data-size="large" data-caption="" data-link-to-gallery="true" data-alignment="center">Loading...</div>
|
||||
|
||||
|
||||
|
||||
<div class="photo-card-block" data-photo-id="1" data-photo-title="vlcsnap-2026-01-09-15h39m52s898.png" data-photo-format="png" data-photo-width="1920" data-photo-height="1040" data-photo-views="0" style="max-width: 480px; margin: 0 auto;">
|
||||
<a href="http://app.org/gallery?expanded=photo-1" style="display: block; text-decoration: none; color: inherit; border-radius: 12px; overflow: hidden; background: #1b2838; box-shadow: 0 4px 12px rgba(0,0,0,0.3);">
|
||||
<div style="position: relative; padding-bottom: 66.67%; background: #0d1b2a; overflow: hidden;">
|
||||
<img src="http://app.orgdata:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22480%22%20height%3D%22320%22%20viewBox%3D%220%200%20480%20320%22%3E%3Crect%20fill%3D%22%230d1b2a%22%20width%3D%22480%22%20height%3D%22320%22%2F%3E%3Ccircle%20cx%3D%22240%22%20cy%3D%22160%22%20r%3D%2232%22%20fill%3D%22rgba(46%2C125%2C50%2C0.6)%22%2F%3E%3Crect%20x%3D%22224%22%20y%3D%22144%22%20width%3D%2232%22%20height%3D%2232%22%20rx%3D%224%22%20fill%3D%22none%22%20stroke%3D%22%23fff%22%20stroke-width%3D%222%22%2F%3E%3Ccircle%20cx%3D%22234%22%20cy%3D%22154%22%20r%3D%223%22%20fill%3D%22%23fff%22%2F%3E%3Cpath%20d%3D%22M256%20176l-10-10L224%20176%22%20fill%3D%22none%22%20stroke%3D%22%23fff%22%20stroke-width%3D%222%22%2F%3E%3C%2Fsvg%3E" alt="vlcsnap-2026-01-09-15h39m52s898.png" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover;" />
|
||||
<span style="position: absolute; top: 8px; left: 8px; background: #2e7d32; color: #fff; font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 4px;">PNG</span>
|
||||
<span style="position: absolute; bottom: 8px; right: 8px; background: rgba(0,0,0,0.8); color: #fff; font-size: 12px; font-weight: 500; padding: 2px 6px; border-radius: 4px;">1920×1040</span>
|
||||
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 56px; height: 56px; background: rgba(0,0,0,0.5); border-radius: 50%; display: flex; align-items: center; justify-content: center;">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding: 12px 16px;">
|
||||
<div style="color: #fff; font-size: 15px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">vlcsnap-2026-01-09-15h39m52s898.png</div>
|
||||
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 6px;">
|
||||
<span style="color: #8899aa; font-size: 13px;">0 views</span>
|
||||
<span style="color: #43cea2; font-size: 13px; font-weight: 500;">View →</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="cm-product-mlrk53ro" style="text-align:center;padding:32px 20px;background:linear-gradient(135deg,#1a1a2e,#16213e);border-radius:12px;margin:16px 0;max-width:420px;margin-left:auto;margin-right:auto;">
|
||||
<div style="width:80px;height:80px;border-radius:12px;background:linear-gradient(135deg,#9d4edd,#722ed1);display:flex;align-items:center;justify-content:center;margin:0 auto 16px;"><span style="font-size:36px;color:#fff;">🛒</span></div>
|
||||
<div style="display:inline-block;padding:2px 10px;border-radius:4px;background:#1890ff;color:#fff;font-size:11px;font-weight:600;margin-bottom:8px;">DIGITAL</div>
|
||||
<h3 style="color:#fff;margin:8px 0 4px;">Test Product 1</h3>
|
||||
<p style="color:rgba(255,255,255,0.7);font-size:0.9rem;margin-bottom:12px;">A test product</p>
|
||||
<p style="color:#fff;font-size:1.4rem;font-weight:700;margin-bottom:20px;">$90.00</p>
|
||||
<div id="cm-product-mlrk53ro-form" style="max-width:320px;margin:0 auto;text-align:left;">
|
||||
<input type="email" id="cm-product-mlrk53ro-email" placeholder="your@email.com *" style="width:100%;padding:10px 14px;background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.25);border-radius:8px;color:#fff;font-size:0.95rem;box-sizing:border-box;margin-bottom:10px;outline:none;" required />
|
||||
<input type="text" id="cm-product-mlrk53ro-name" placeholder="Name (optional)" style="width:100%;padding:10px 14px;background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.25);border-radius:8px;color:#fff;font-size:0.95rem;box-sizing:border-box;margin-bottom:10px;outline:none;" />
|
||||
<div id="cm-product-mlrk53ro-error" style="color:#ff4d4f;font-size:0.9rem;margin-bottom:8px;display:none;"></div>
|
||||
<button type="button" id="cm-product-mlrk53ro-submit" style="width:100%;padding:14px 24px;background:#722ed1;color:#fff;border:none;border-radius:8px;font-weight:600;font-size:1.05rem;cursor:pointer;">Buy Now — $90.00</button>
|
||||
<p style="margin-top:12px;font-size:0.75rem;color:rgba(255,255,255,0.4);text-align:center;">
|
||||
Secure payment via Stripe. <a href="http://app.org/shop" style="color:rgba(255,255,255,0.5);">Browse all products</a>
|
||||
</p>
|
||||
</div>
|
||||
<noscript><a href="http://app.org/shop" style="display:inline-block;padding:14px 36px;background:#722ed1;color:#fff;text-decoration:none;border-radius:8px;font-weight:600;">View in Shop</a></noscript>
|
||||
</div>
|
||||
<script>
|
||||
(function(){
|
||||
var root=document.getElementById('cm-product-mlrk53ro');
|
||||
if(!root)return;
|
||||
var apiUrl='http://api.org';
|
||||
var productId='cmlritfdm0000mkbgf9eelg4t';
|
||||
var submitBtn=document.getElementById('cm-product-mlrk53ro-submit');
|
||||
var emailInput=document.getElementById('cm-product-mlrk53ro-email');
|
||||
var nameInput=document.getElementById('cm-product-mlrk53ro-name');
|
||||
var errDiv=document.getElementById('cm-product-mlrk53ro-error');
|
||||
function showErr(msg){errDiv.textContent=msg;errDiv.style.display='block';}
|
||||
function hideErr(){errDiv.style.display='none';}
|
||||
submitBtn.addEventListener('click',function(){
|
||||
hideErr();
|
||||
var email=(emailInput.value||'').trim();
|
||||
if(!email||email.indexOf('@')<1){showErr('Please enter a valid email address.');return;}
|
||||
submitBtn.disabled=true;submitBtn.textContent='Processing...';
|
||||
fetch(apiUrl+'/api/payments/purchase',{
|
||||
method:'POST',headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({productId:productId,buyerEmail:email,buyerName:(nameInput.value||'').trim()||undefined})
|
||||
}).then(function(r){return r.json();}).then(function(data){
|
||||
if(data.url){window.location.href=data.url;}
|
||||
else{showErr(data.error&&data.error.message||'Something went wrong.');submitBtn.disabled=false;submitBtn.textContent='Buy Now';}
|
||||
}).catch(function(){showErr('Connection error. Please try again.');submitBtn.disabled=false;submitBtn.textContent='Buy Now';});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<div id="cm-donate-mlrjofs1" style="text-align:center;padding:40px 20px;background:linear-gradient(135deg,#2d1b69,#1a1a2e);border-radius:12px;margin:16px 0;max-width:560px;margin-left:auto;margin-right:auto;">
|
||||
<p style="font-size:48px;margin:0;">❤️</p>
|
||||
<h2 style="color:#fff;margin:12px 0;">Support Our Work</h2>
|
||||
<p style="color:rgba(255,255,255,0.8);margin-bottom:24px;">Every contribution makes a difference. Choose an amount below.</p>
|
||||
|
||||
<div id="cm-donate-mlrjofs1-amounts" style="margin-bottom: 16px;">
|
||||
<button type="button" class="cm-donate-mlrjofs1-amt" data-cents="1000" style="display:inline-block;padding:10px 22px;margin:4px;color:#fff;text-decoration:none;border-radius:8px;font-weight:600;font-size:1rem;cursor:pointer;border:2px solid rgba(255,255,255,0.25);background:rgba(255,255,255,0.08);">$10</button>
|
||||
<button type="button" class="cm-donate-mlrjofs1-amt" data-cents="2500" style="display:inline-block;padding:10px 22px;margin:4px;color:#fff;text-decoration:none;border-radius:8px;font-weight:600;font-size:1rem;cursor:pointer;border:2px solid rgba(255,255,255,0.25);background:rgba(255,255,255,0.08);">$25</button>
|
||||
<button type="button" class="cm-donate-mlrjofs1-amt" data-cents="5000" style="display:inline-block;padding:10px 22px;margin:4px;color:#fff;text-decoration:none;border-radius:8px;font-weight:600;font-size:1rem;cursor:pointer;border:2px solid rgba(255,255,255,0.25);background:rgba(255,255,255,0.08);">$50</button>
|
||||
<button type="button" class="cm-donate-mlrjofs1-amt" data-cents="10000" style="display:inline-block;padding:10px 22px;margin:4px;color:#fff;text-decoration:none;border-radius:8px;font-weight:600;font-size:1rem;cursor:pointer;border:2px solid rgba(255,255,255,0.25);background:rgba(255,255,255,0.08);">$100</button>
|
||||
<button type="button" class="cm-donate-mlrjofs1-amt" data-cents="custom" style="display:inline-block;padding:10px 22px;margin:4px;color:#fff;text-decoration:none;border-radius:8px;font-weight:600;font-size:1rem;cursor:pointer;border:2px solid rgba(255,255,255,0.25);background:rgba(255,255,255,0.08);">Custom</button>
|
||||
</div>
|
||||
<div id="cm-donate-mlrjofs1-custom" style="display:none;margin-bottom:12px;">
|
||||
<input type="number" id="cm-donate-mlrjofs1-custom-input" min="1" step="1" placeholder="Enter amount ($)" style="width:100%;padding:10px 14px;background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.25);border-radius:8px;color:#fff;font-size:0.95rem;box-sizing:border-box;margin-bottom:10px;outline:none;" />
|
||||
</div>
|
||||
<div id="cm-donate-mlrjofs1-form" style="display:none;max-width:360px;margin:0 auto;text-align:left;">
|
||||
<input type="email" id="cm-donate-mlrjofs1-email" placeholder="your@email.com *" style="width:100%;padding:10px 14px;background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.25);border-radius:8px;color:#fff;font-size:0.95rem;box-sizing:border-box;margin-bottom:10px;outline:none;" required />
|
||||
<input type="text" id="cm-donate-mlrjofs1-name" placeholder="Name (optional)" style="width:100%;padding:10px 14px;background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.25);border-radius:8px;color:#fff;font-size:0.95rem;box-sizing:border-box;margin-bottom:10px;outline:none;" />
|
||||
<div style="margin-bottom:12px;color:rgba(255,255,255,0.7);font-size:0.85rem;">
|
||||
<label><input type="checkbox" id="cm-donate-mlrjofs1-anon" style="margin-right:6px;" />Make my donation anonymous</label>
|
||||
</div>
|
||||
<div id="cm-donate-mlrjofs1-error" style="color:#ff4d4f;font-size:0.9rem;margin-bottom:8px;display:none;"></div>
|
||||
<button type="button" id="cm-donate-mlrjofs1-submit" style="width:100%;padding:14px 24px;background:#eb2f96;color:#fff;border:none;border-radius:8px;font-weight:600;font-size:1.05rem;cursor:pointer;">
|
||||
Donate
|
||||
</button>
|
||||
<p style="margin-top:12px;font-size:0.75rem;color:rgba(255,255,255,0.4);text-align:center;">
|
||||
Secure payment via Stripe. <a href="http://app.org/donate" style="color:rgba(255,255,255,0.5);">Open full donate page</a>
|
||||
</p>
|
||||
</div>
|
||||
<noscript><a href="http://app.org/donate" style="display:inline-block;padding:14px 36px;background:#eb2f96;color:#fff;text-decoration:none;border-radius:8px;font-weight:600;">Donate Now</a></noscript>
|
||||
</div>
|
||||
<script>
|
||||
(function(){
|
||||
var root=document.getElementById('cm-donate-mlrjofs1');
|
||||
if(!root)return;
|
||||
var apiUrl='http://api.org';
|
||||
var selectedCents=0;
|
||||
var form=document.getElementById('cm-donate-mlrjofs1-form');
|
||||
var submitBtn=document.getElementById('cm-donate-mlrjofs1-submit');
|
||||
var emailInput=document.getElementById('cm-donate-mlrjofs1-email');
|
||||
var nameInput=document.getElementById('cm-donate-mlrjofs1-name');
|
||||
var anonBox=document.getElementById('cm-donate-mlrjofs1-anon');
|
||||
var errDiv=document.getElementById('cm-donate-mlrjofs1-error');
|
||||
var amtBtns=root.querySelectorAll('.cm-donate-mlrjofs1-amt');
|
||||
var customWrap=document.getElementById('cm-donate-mlrjofs1-custom');
|
||||
var customInput=document.getElementById('cm-donate-mlrjofs1-custom-input');
|
||||
var activeStyle='background:#eb2f96;border-color:#eb2f96;';
|
||||
var baseStyle='background:rgba(255,255,255,0.08);border-color:rgba(255,255,255,0.25);';
|
||||
function selectAmt(btn,cents){
|
||||
amtBtns.forEach(function(b){b.style.cssText='display:inline-block;padding:10px 22px;margin:4px;color:#fff;text-decoration:none;border-radius:8px;font-weight:600;font-size:1rem;cursor:pointer;border:2px solid rgba(255,255,255,0.25);background:rgba(255,255,255,0.08);'+baseStyle;});
|
||||
btn.style.cssText='display:inline-block;padding:10px 22px;margin:4px;color:#fff;text-decoration:none;border-radius:8px;font-weight:600;font-size:1rem;cursor:pointer;border:2px solid rgba(255,255,255,0.25);background:rgba(255,255,255,0.08);'+activeStyle;
|
||||
if(cents==='custom'){
|
||||
selectedCents=0;customWrap.style.display='block';
|
||||
customInput.focus();
|
||||
submitBtn.textContent='Donate';
|
||||
}else{
|
||||
selectedCents=parseInt(cents,10);customWrap.style.display='none';
|
||||
submitBtn.textContent='Donate $'+(selectedCents/100).toFixed(0);
|
||||
}
|
||||
form.style.display='block';
|
||||
}
|
||||
amtBtns.forEach(function(b){
|
||||
b.addEventListener('click',function(){selectAmt(b,b.getAttribute('data-cents'));});
|
||||
});
|
||||
if(customInput){customInput.addEventListener('input',function(){
|
||||
var v=parseFloat(customInput.value);
|
||||
if(v>0){selectedCents=Math.round(v*100);submitBtn.textContent='Donate $'+v.toFixed(2);}
|
||||
else{selectedCents=0;submitBtn.textContent='Donate';}
|
||||
});}
|
||||
function showErr(msg){errDiv.textContent=msg;errDiv.style.display='block';}
|
||||
function hideErr(){errDiv.style.display='none';}
|
||||
submitBtn.addEventListener('click',function(){
|
||||
hideErr();
|
||||
var email=(emailInput.value||'').trim();
|
||||
if(!email||email.indexOf('@')<1){showErr('Please enter a valid email address.');return;}
|
||||
if(!selectedCents||selectedCents<100){showErr('Please select a donation amount.');return;}
|
||||
submitBtn.disabled=true;submitBtn.textContent='Processing...';
|
||||
fetch(apiUrl+'/api/payments/donate',{
|
||||
method:'POST',headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({amountCents:selectedCents,email:email,name:(nameInput.value||'').trim()||undefined,isAnonymous:!!(anonBox&&anonBox.checked)})
|
||||
}).then(function(r){return r.json();}).then(function(data){
|
||||
if(data.url){window.location.href=data.url;}
|
||||
else{showErr(data.error&&data.error.message||'Something went wrong.');submitBtn.disabled=false;submitBtn.textContent='Donate';}
|
||||
}).catch(function(){showErr('Connection error. Please try again.');submitBtn.disabled=false;submitBtn.textContent='Donate';});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
<div style="text-align: center; padding: 40px 20px; background: linear-gradient(135deg, #2d1b69, #1a1a2e); border-radius: 12px; margin: 16px 0; max-width: 560px; margin-left: auto; margin-right: auto;">
|
||||
<p style="font-size: 48px; margin: 0;">❤️</p>
|
||||
<h2 style="color: #fff; margin: 12px 0;">Support Our Work</h2>
|
||||
<p style="color: rgba(255,255,255,0.8); margin-bottom: 24px;">Every contribution makes a difference. Choose an amount below.</p>
|
||||
<div style="margin-bottom: 16px;">
|
||||
<a href="http://app.org/donate?amount=1000" style="display: inline-block; padding: 10px 22px; margin: 4px; background: rgba(255,255,255,0.12); color: #fff; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 1rem; border: 2px solid rgba(255,255,255,0.2);">$10</a>
|
||||
<a href="http://app.org/donate?amount=2500" style="display: inline-block; padding: 10px 22px; margin: 4px; background: rgba(255,255,255,0.12); color: #fff; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 1rem; border: 2px solid rgba(255,255,255,0.2);">$25</a>
|
||||
<a href="http://app.org/donate?amount=5000" style="display: inline-block; padding: 10px 22px; margin: 4px; background: rgba(255,255,255,0.12); color: #fff; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 1rem; border: 2px solid rgba(255,255,255,0.2);">$50</a>
|
||||
<a href="http://app.org/donate?amount=10000" style="display: inline-block; padding: 10px 22px; margin: 4px; background: rgba(255,255,255,0.12); color: #fff; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 1rem; border: 2px solid rgba(255,255,255,0.2);">$100</a>
|
||||
</div>
|
||||
<a href="http://app.org/donate" style="display: inline-block; padding: 10px 22px; margin: 4px; background: #eb2f96; color: #fff; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 1rem;">Custom Amount</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="text-align: center; padding: 40px 20px; background: linear-gradient(135deg, #9d4edd, #722ed1); border-radius: 12px; margin: 16px 0;">
|
||||
<p style="font-size: 48px; margin: 0;">🛒</p>
|
||||
<h2 style="color: #fff; margin: 12px 0;">Browse Our Products</h2>
|
||||
<p style="color: rgba(255,255,255,0.8); margin-bottom: 24px;">Reports, toolkits, event tickets, and more.</p>
|
||||
<a href="http://app.org/shop" style="display: inline-block; padding: 14px 36px; background: #fff; color: #722ed1; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 1.1rem;">Shop Now</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="text-align: center; padding: 40px 20px; background: linear-gradient(135deg, #1a1a2e, #16213e); border-radius: 12px; margin: 16px 0;">
|
||||
<h2 style="color: #fff; margin: 12px 0;">Choose Your Plan</h2>
|
||||
<p style="color: rgba(255,255,255,0.8); margin-bottom: 24px;">Get access to exclusive content and features.</p>
|
||||
<a href="http://app.org/pricing" style="display: inline-block; padding: 14px 36px; background: #722ed1; color: #fff; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 1.1rem;">View Plans</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div style="text-align: center; padding: 40px 20px; background: linear-gradient(135deg, #2d1b69, #1a1a2e); border-radius: 12px; margin: 16px 0;">
|
||||
<p style="font-size: 48px; margin: 0;">❤️</p>
|
||||
<h2 style="color: #fff; margin: 12px 0;">Support Our Cause</h2>
|
||||
<p style="color: rgba(255,255,255,0.8); margin-bottom: 24px;">Your contribution helps us create lasting change in our community.</p>
|
||||
<a href="http://app.org/donate" style="display: inline-block; padding: 14px 36px; background: #eb2f96; color: #fff; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 1.1rem;">Donate Now</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="video-card-block" data-video-id="2" data-video-title="Testing This Sucker" data-video-duration="594" data-video-quality="" data-video-views="0" style="max-width: 480px; margin: 0 auto;">
|
||||
<a href="http://app.org/gallery/watch/2" style="display: block; text-decoration: none; color: inherit; border-radius: 12px; overflow: hidden; background: #1b2838; box-shadow: 0 4px 12px rgba(0,0,0,0.3);">
|
||||
<div style="position: relative; padding-bottom: 56.25%; background: #0d1b2a; overflow: hidden;">
|
||||
<img src="http://app.orgdata:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22480%22%20height%3D%22270%22%20viewBox%3D%220%200%20480%20270%22%3E%3Crect%20fill%3D%22%230d1b2a%22%20width%3D%22480%22%20height%3D%22270%22%2F%3E%3Ccircle%20cx%3D%22240%22%20cy%3D%22135%22%20r%3D%2232%22%20fill%3D%22rgba(157%2C78%2C221%2C0.6)%22%2F%3E%3Cpolygon%20points%3D%22230%2C118%20258%2C135%20230%2C152%22%20fill%3D%22%23fff%22%2F%3E%3C%2Fsvg%3E" alt="Testing This Sucker" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover;" />
|
||||
|
||||
<span style="position: absolute; bottom: 8px; right: 8px; background: rgba(0,0,0,0.8); color: #fff; font-size: 12px; font-weight: 500; padding: 2px 6px; border-radius: 4px;">9:54</span>
|
||||
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 56px; height: 56px; background: rgba(0,0,0,0.5); border-radius: 50%; display: flex; align-items: center; justify-content: center;">
|
||||
<svg width="24" height="24" viewBox="0 0 20 20" fill="#fff"><path d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding: 12px 16px;">
|
||||
<div style="color: #fff; font-size: 15px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">Testing This Sucker</div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 6px;">
|
||||
<span style="color: #8899aa; font-size: 13px;">0 views</span>
|
||||
<span style="color: #9d4edd; font-size: 13px; font-weight: 500;">Watch →</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
# testing
|
||||
# testing
|
||||
|
||||
## Wiki-Link Tests
|
||||
|
||||
- Doc link: [[installation]]
|
||||
- Doc link with display text: [[installation|Install Guide]]
|
||||
- Doc link with anchor: [[installation#prerequisites]]
|
||||
- Image embed: ![[logo.png]]
|
||||
- Image with alt: ![[logo.png|Site Logo]]
|
||||
- Code block (should NOT be converted):
|
||||
|
||||
```
|
||||
[[this-should-stay-as-is]]
|
||||
```
|
||||
|
||||
- Inline code (should NOT be converted): `[[not-a-link]]`
|
||||
- Unresolved link (should stay as-is): [[nonexistent-page]]
|
||||
|
||||
@ -9,7 +9,7 @@ use_directory_urls: true
|
||||
# Repository
|
||||
repo_url: https://gitea.bnkops.com/admin/changemaker.lite
|
||||
repo_name: changemaker.lite
|
||||
edit_uri: src/branch/main/mkdocs/docs
|
||||
edit_uri: src/branch/v2/mkdocs/docs
|
||||
|
||||
# Theme
|
||||
theme:
|
||||
@ -39,9 +39,14 @@ theme:
|
||||
- content.action.view
|
||||
- content.code.annotate
|
||||
- content.code.copy
|
||||
- content.code.select
|
||||
- content.tabs.link
|
||||
- content.tooltips
|
||||
- navigation.footer
|
||||
- navigation.indexes
|
||||
- navigation.instant
|
||||
- navigation.instant.prefetch
|
||||
- navigation.instant.progress
|
||||
- navigation.path
|
||||
- navigation.prune
|
||||
- navigation.tabs
|
||||
@ -66,6 +71,8 @@ plugins:
|
||||
post_date_format: medium
|
||||
archive_name: Archive
|
||||
categories_name: Categories
|
||||
authors: true
|
||||
authors_file: blog/.authors.yml
|
||||
- tags
|
||||
|
||||
# Extra CSS and JS
|
||||
@ -125,7 +132,9 @@ markdown_extensions:
|
||||
- pymdownx.keys
|
||||
- pymdownx.mark
|
||||
- pymdownx.smartsymbols
|
||||
- pymdownx.snippets
|
||||
- pymdownx.snippets:
|
||||
auto_append:
|
||||
- includes/abbreviations.md
|
||||
- pymdownx.superfences:
|
||||
custom_fences:
|
||||
- name: mermaid
|
||||
@ -142,9 +151,22 @@ markdown_extensions:
|
||||
extra:
|
||||
analytics:
|
||||
provider: custom
|
||||
consent:
|
||||
title: Cookie consent
|
||||
description: >
|
||||
We use cookies to recognize your repeated visits and preferences,
|
||||
as well as to measure the effectiveness of our documentation.
|
||||
With your consent, you help us improve.
|
||||
actions:
|
||||
- accept
|
||||
- reject
|
||||
- manage
|
||||
generator: false
|
||||
status:
|
||||
new: Recently added
|
||||
deprecated: Legacy
|
||||
social:
|
||||
- icon: fontawesome/brands/github
|
||||
- icon: fontawesome/solid/code-branch
|
||||
link: https://gitea.bnkops.com/admin
|
||||
name: Gitea Repository
|
||||
- icon: fontawesome/solid/paper-plane
|
||||
@ -153,7 +175,7 @@ extra:
|
||||
|
||||
# Copyright
|
||||
copyright: >
|
||||
Copyright © 2024 The Bunker Operations –
|
||||
Copyright © 2024–2026 The Bunker Operations –
|
||||
<a href="#__consent">Change cookie settings</a>
|
||||
|
||||
# Navigation
|
||||
|
||||