Tonne of udpatess
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 64 KiB |
@@ -20,7 +20,7 @@
|
||||
"assets/images/social/config/map.png": "322276b5f574c461e2cafc4c5c8e8735bed2d25e",
|
||||
"assets/images/social/config/mkdocs.png": "b23831844fca01c49624fa378a20453daae66604",
|
||||
"assets/images/social/docs/admin/index.png": "fca983b7d9dc1cbac915d8369f2d6354e3902fa5",
|
||||
"assets/images/social/docs/api/index.png": "6d589b655cd1681d187337b6c46275d9c4758361",
|
||||
"assets/images/social/docs/api/index.png": "9c1f1fae7609895b60edf224e31c2be04ddd0232",
|
||||
"assets/images/social/docs/architecture/index.png": "480bf36086518125519527af26a31c3be8b1d04c",
|
||||
"assets/images/social/docs/deployment/index.png": "2ef61cacc08a8a0f6bc94308936ec7731fe5938a",
|
||||
"assets/images/social/docs/features/index.png": "f092b70692e778c4f570fb7942113ceb36f5110c",
|
||||
@@ -36,6 +36,7 @@
|
||||
"assets/images/social/main.png": "42e4265e567d9c39351f377e4b72c40f808f0bac",
|
||||
"assets/images/social/manual/index.png": "82996eb5279275b45780cc6db42d91610eac6984",
|
||||
"assets/images/social/manual/map.png": "322276b5f574c461e2cafc4c5c8e8735bed2d25e",
|
||||
"assets/images/social/partials/integrations/analytics/custom.png": "7a6c69ad7d9364ad84c37833ae5f55b20fbea372",
|
||||
"assets/images/social/phil/cost-comparison.png": "91217f5184ec32a24c77b88153b8f30740ba68ec",
|
||||
"assets/images/social/phil/index.png": "f26d709331027166d2fc17f40efc2470f47bd89f",
|
||||
"assets/images/social/services/code-server.png": "f5f93704685a0852de6e634da3ceb3d0fc549897",
|
||||
|
||||
54
mkdocs/docs/assets/css/payment-widgets.css
Normal file
@@ -0,0 +1,54 @@
|
||||
/* Payment Widget Hydration Styles for MkDocs */
|
||||
|
||||
/* Amount selection buttons */
|
||||
.cm-amt-btn {
|
||||
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);
|
||||
transition: background 0.2s, border-color 0.2s;
|
||||
}
|
||||
|
||||
.cm-amt-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.cm-amt-active {
|
||||
background: #eb2f96 !important;
|
||||
border-color: #eb2f96 !important;
|
||||
}
|
||||
|
||||
/* Fallback when API is unreachable */
|
||||
.cm-payment-fallback {
|
||||
display: none;
|
||||
text-align: center;
|
||||
padding: 12px 16px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.cm-payment-fallback a {
|
||||
display: inline-block;
|
||||
padding: 14px 36px;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
/* Mobile adjustments */
|
||||
@media (max-width: 480px) {
|
||||
.cm-amt-btn {
|
||||
padding: 8px 16px;
|
||||
font-size: 0.9rem;
|
||||
margin: 3px;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,17 @@
|
||||
// Auto-generated video player configuration from environment variables
|
||||
// Auto-generated configuration from environment variables
|
||||
// Generated by mkdocs/docs/hooks/env_config_hook.py
|
||||
(function() {
|
||||
window.MEDIA_API_URL = 'http://localhost:4100';
|
||||
window.PUBLIC_URL = 'http://localhost:3002';
|
||||
window.PAYMENT_API_URL = 'http://localhost:4000';
|
||||
window.APP_URL = 'http://localhost:3002';
|
||||
window.VIDEO_PLAYER_DEBUG = false;
|
||||
|
||||
console.log('[Video Config] Loaded from environment:', {
|
||||
console.log('[EnvConfig] Loaded from environment:', {
|
||||
mediaApiUrl: window.MEDIA_API_URL,
|
||||
publicUrl: window.PUBLIC_URL,
|
||||
paymentApiUrl: window.PAYMENT_API_URL,
|
||||
appUrl: window.APP_URL,
|
||||
debug: window.VIDEO_PLAYER_DEBUG
|
||||
});
|
||||
})();
|
||||
251
mkdocs/docs/assets/js/payment-widgets.js
Normal file
@@ -0,0 +1,251 @@
|
||||
/**
|
||||
* Payment Widget Hydration for MkDocs
|
||||
*
|
||||
* Automatically converts payment block placeholders into interactive widgets
|
||||
* when landing pages with payment blocks are exported to MkDocs.
|
||||
*
|
||||
* Reads configuration from window.PAYMENT_API_URL and window.APP_URL
|
||||
* (set by env-config.js hook). No hardcoded URLs.
|
||||
*
|
||||
* Supports:
|
||||
* - Donate blocks: [id^="cm-donate-"]
|
||||
* - Product blocks: [id^="cm-product-"]
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var PAYMENT_API_URL = window.PAYMENT_API_URL || '';
|
||||
var APP_URL = window.APP_URL || '';
|
||||
|
||||
/**
|
||||
* Check API health before hydrating
|
||||
*/
|
||||
function checkApiHealth(callback) {
|
||||
if (!PAYMENT_API_URL) {
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', PAYMENT_API_URL + '/api/payments/config', true);
|
||||
xhr.timeout = 5000;
|
||||
xhr.onload = function() { callback(xhr.status >= 200 && xhr.status < 400); };
|
||||
xhr.onerror = function() { callback(false); };
|
||||
xhr.ontimeout = function() { callback(false); };
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate a donate block
|
||||
*/
|
||||
function hydrateDonateBlock(el) {
|
||||
if (el.getAttribute('data-hydrated') === 'true') return;
|
||||
|
||||
var submitBtn = el.querySelector('[data-role="submit"]');
|
||||
if (!submitBtn) return; // Legacy block with inline script — skip
|
||||
|
||||
el.setAttribute('data-hydrated', 'true');
|
||||
|
||||
var emailInput = el.querySelector('[data-role="email"]');
|
||||
var nameInput = el.querySelector('[data-role="name"]');
|
||||
var anonBox = el.querySelector('[data-role="anonymous"]');
|
||||
var errDiv = el.querySelector('[data-role="error"]');
|
||||
var formDiv = el.querySelector('[data-role="form"]');
|
||||
var amountsDiv = el.querySelector('[data-role="amounts"]');
|
||||
var customWrap = el.querySelector('[data-role="custom-wrap"]');
|
||||
var customInput = el.querySelector('[data-role="custom-input"]');
|
||||
|
||||
var preselected = parseInt(el.getAttribute('data-preselected') || '0', 10);
|
||||
var showPicker = el.getAttribute('data-show-picker') === 'true';
|
||||
var selectedCents = preselected || 0;
|
||||
|
||||
function showErr(msg) {
|
||||
if (errDiv) { errDiv.textContent = msg; errDiv.style.display = 'block'; }
|
||||
}
|
||||
function hideErr() {
|
||||
if (errDiv) { errDiv.style.display = 'none'; }
|
||||
}
|
||||
|
||||
// Amount button logic
|
||||
if (showPicker && amountsDiv) {
|
||||
var amtBtns = amountsDiv.querySelectorAll('.cm-amt-btn');
|
||||
amtBtns.forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
var cents = btn.getAttribute('data-cents');
|
||||
// Deactivate all
|
||||
amtBtns.forEach(function(b) { b.classList.remove('cm-amt-active'); });
|
||||
btn.classList.add('cm-amt-active');
|
||||
|
||||
if (cents === 'custom') {
|
||||
selectedCents = 0;
|
||||
if (customWrap) customWrap.style.display = 'block';
|
||||
if (customInput) customInput.focus();
|
||||
submitBtn.textContent = 'Donate';
|
||||
} else {
|
||||
selectedCents = parseInt(cents, 10);
|
||||
if (customWrap) customWrap.style.display = 'none';
|
||||
submitBtn.textContent = 'Donate $' + (selectedCents / 100).toFixed(0);
|
||||
}
|
||||
if (formDiv) formDiv.style.display = 'block';
|
||||
});
|
||||
});
|
||||
|
||||
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';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Submit handler
|
||||
submitBtn.addEventListener('click', function() {
|
||||
hideErr();
|
||||
var email = (emailInput && 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(PAYMENT_API_URL + '/api/payments/donate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
amountCents: selectedCents,
|
||||
email: email,
|
||||
name: (nameInput && 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';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate a product block
|
||||
*/
|
||||
function hydrateProductBlock(el) {
|
||||
if (el.getAttribute('data-hydrated') === 'true') return;
|
||||
|
||||
var submitBtn = el.querySelector('[data-role="submit"]');
|
||||
if (!submitBtn) return; // Legacy or sold-out — skip
|
||||
|
||||
el.setAttribute('data-hydrated', 'true');
|
||||
|
||||
var emailInput = el.querySelector('[data-role="email"]');
|
||||
var nameInput = el.querySelector('[data-role="name"]');
|
||||
var errDiv = el.querySelector('[data-role="error"]');
|
||||
var productId = el.getAttribute('data-product-id') || '';
|
||||
|
||||
function showErr(msg) {
|
||||
if (errDiv) { errDiv.textContent = msg; errDiv.style.display = 'block'; }
|
||||
}
|
||||
function hideErr() {
|
||||
if (errDiv) { errDiv.style.display = 'none'; }
|
||||
}
|
||||
|
||||
submitBtn.addEventListener('click', function() {
|
||||
hideErr();
|
||||
var email = (emailInput && emailInput.value || '').trim();
|
||||
if (!email || email.indexOf('@') < 1) { showErr('Please enter a valid email address.'); return; }
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = 'Processing...';
|
||||
|
||||
fetch(PAYMENT_API_URL + '/api/payments/purchase', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
productId: productId,
|
||||
buyerEmail: email,
|
||||
buyerName: (nameInput && 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';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show fallback links when API is unreachable
|
||||
*/
|
||||
function showFallbacks() {
|
||||
var fallbacks = document.querySelectorAll('.cm-payment-fallback');
|
||||
fallbacks.forEach(function(f) {
|
||||
f.style.display = 'block';
|
||||
});
|
||||
// Hide interactive forms
|
||||
document.querySelectorAll('[id^="cm-donate-"] [data-role="form"], [id^="cm-donate-"] [data-role="amounts"]').forEach(function(el) {
|
||||
el.style.display = 'none';
|
||||
});
|
||||
document.querySelectorAll('[id^="cm-product-"] [data-role="form"]').forEach(function(el) {
|
||||
el.style.display = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all payment widgets
|
||||
*/
|
||||
function initAll() {
|
||||
var donateBlocks = document.querySelectorAll('[id^="cm-donate-"]');
|
||||
var productBlocks = document.querySelectorAll('[id^="cm-product-"]');
|
||||
|
||||
if (donateBlocks.length === 0 && productBlocks.length === 0) return;
|
||||
|
||||
checkApiHealth(function(healthy) {
|
||||
if (healthy) {
|
||||
donateBlocks.forEach(hydrateDonateBlock);
|
||||
productBlocks.forEach(hydrateProductBlock);
|
||||
} else {
|
||||
console.warn('[Payment Widgets] API unreachable, showing fallback links');
|
||||
showFallbacks();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initAll);
|
||||
} else {
|
||||
initAll();
|
||||
}
|
||||
|
||||
// Re-initialize on MkDocs navigation (for SPA-style navigation)
|
||||
if (typeof window.document$ !== 'undefined') {
|
||||
window.document$.subscribe(function() {
|
||||
setTimeout(initAll, 100);
|
||||
});
|
||||
}
|
||||
})();
|
||||
@@ -7,10 +7,10 @@
|
||||
"stars_count": 0,
|
||||
"forks_count": 0,
|
||||
"open_issues_count": 23,
|
||||
"updated_at": "2026-02-17T10:36:57-07:00",
|
||||
"updated_at": "2026-02-17T15:49:30-07:00",
|
||||
"created_at": "2025-05-28T14:54:59-06:00",
|
||||
"clone_url": "https://gitea.bnkops.com/admin/changemaker.lite.git",
|
||||
"ssh_url": "git@gitea.bnkops.com:admin/changemaker.lite.git",
|
||||
"default_branch": "main",
|
||||
"last_build_update": "2026-02-17T10:36:57-07:00"
|
||||
"last_build_update": "2026-02-17T15:49:30-07:00"
|
||||
}
|
||||
@@ -4,10 +4,10 @@
|
||||
"description": "Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.",
|
||||
"html_url": "https://github.com/anthropics/claude-code",
|
||||
"language": "Shell",
|
||||
"stars_count": 67351,
|
||||
"forks_count": 5255,
|
||||
"open_issues_count": 6188,
|
||||
"updated_at": "2026-02-17T21:23:40Z",
|
||||
"stars_count": 67411,
|
||||
"forks_count": 5260,
|
||||
"open_issues_count": 6231,
|
||||
"updated_at": "2026-02-18T04:18:30Z",
|
||||
"created_at": "2025-02-22T17:41:21Z",
|
||||
"clone_url": "https://github.com/anthropics/claude-code.git",
|
||||
"ssh_url": "git@github.com:anthropics/claude-code.git",
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
"description": "VS Code in the browser",
|
||||
"html_url": "https://github.com/coder/code-server",
|
||||
"language": "TypeScript",
|
||||
"stars_count": 76278,
|
||||
"forks_count": 6513,
|
||||
"stars_count": 76281,
|
||||
"forks_count": 6512,
|
||||
"open_issues_count": 176,
|
||||
"updated_at": "2026-02-17T20:24:30Z",
|
||||
"updated_at": "2026-02-18T03:57:21Z",
|
||||
"created_at": "2019-02-27T16:50:41Z",
|
||||
"clone_url": "https://github.com/coder/code-server.git",
|
||||
"ssh_url": "git@github.com:coder/code-server.git",
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "A highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations.",
|
||||
"html_url": "https://github.com/gethomepage/homepage",
|
||||
"language": "JavaScript",
|
||||
"stars_count": 28451,
|
||||
"stars_count": 28454,
|
||||
"forks_count": 1790,
|
||||
"open_issues_count": 1,
|
||||
"updated_at": "2026-02-17T19:07:13Z",
|
||||
"updated_at": "2026-02-18T03:37:12Z",
|
||||
"created_at": "2022-08-24T07:29:42Z",
|
||||
"clone_url": "https://github.com/gethomepage/homepage.git",
|
||||
"ssh_url": "git@github.com:gethomepage/homepage.git",
|
||||
"default_branch": "dev",
|
||||
"last_build_update": "2026-02-17T12:23:04Z"
|
||||
"last_build_update": "2026-02-18T00:48:06Z"
|
||||
}
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "Git with a cup of tea! Painless self-hosted all-in-one software development service, including Git hosting, code review, team collaboration, package registry and CI/CD",
|
||||
"html_url": "https://github.com/go-gitea/gitea",
|
||||
"language": "Go",
|
||||
"stars_count": 53755,
|
||||
"forks_count": 6390,
|
||||
"open_issues_count": 2835,
|
||||
"updated_at": "2026-02-17T20:31:54Z",
|
||||
"stars_count": 53756,
|
||||
"forks_count": 6393,
|
||||
"open_issues_count": 2831,
|
||||
"updated_at": "2026-02-18T04:13:31Z",
|
||||
"created_at": "2016-11-01T02:13:26Z",
|
||||
"clone_url": "https://github.com/go-gitea/gitea.git",
|
||||
"ssh_url": "git@github.com:go-gitea/gitea.git",
|
||||
"default_branch": "main",
|
||||
"last_build_update": "2026-02-17T20:32:46Z"
|
||||
"last_build_update": "2026-02-18T04:14:21Z"
|
||||
}
|
||||
@@ -4,10 +4,10 @@
|
||||
"description": "High performance, self-hosted, newsletter and mailing list manager with a modern dashboard. Single binary app.",
|
||||
"html_url": "https://github.com/knadh/listmonk",
|
||||
"language": "Go",
|
||||
"stars_count": 19074,
|
||||
"stars_count": 19075,
|
||||
"forks_count": 1926,
|
||||
"open_issues_count": 113,
|
||||
"updated_at": "2026-02-17T16:51:23Z",
|
||||
"updated_at": "2026-02-17T21:39:10Z",
|
||||
"created_at": "2019-06-26T05:08:39Z",
|
||||
"clone_url": "https://github.com/knadh/listmonk.git",
|
||||
"ssh_url": "git@github.com:knadh/listmonk.git",
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
"description": "Create & scan cute qr codes easily \ud83d\udc7e",
|
||||
"html_url": "https://github.com/lyqht/mini-qr",
|
||||
"language": "Vue",
|
||||
"stars_count": 1851,
|
||||
"stars_count": 1852,
|
||||
"forks_count": 236,
|
||||
"open_issues_count": 21,
|
||||
"updated_at": "2026-02-17T15:35:23Z",
|
||||
"updated_at": "2026-02-17T22:05:48Z",
|
||||
"created_at": "2023-04-21T14:20:14Z",
|
||||
"clone_url": "https://github.com/lyqht/mini-qr.git",
|
||||
"ssh_url": "git@github.com:lyqht/mini-qr.git",
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.",
|
||||
"html_url": "https://github.com/n8n-io/n8n",
|
||||
"language": "TypeScript",
|
||||
"stars_count": 174947,
|
||||
"forks_count": 54925,
|
||||
"open_issues_count": 1388,
|
||||
"updated_at": "2026-02-17T21:23:05Z",
|
||||
"stars_count": 174994,
|
||||
"forks_count": 54934,
|
||||
"open_issues_count": 1392,
|
||||
"updated_at": "2026-02-18T04:21:26Z",
|
||||
"created_at": "2019-06-22T09:24:21Z",
|
||||
"clone_url": "https://github.com/n8n-io/n8n.git",
|
||||
"ssh_url": "git@github.com:n8n-io/n8n.git",
|
||||
"default_branch": "master",
|
||||
"last_build_update": "2026-02-17T21:12:15Z"
|
||||
"last_build_update": "2026-02-18T01:57:27Z"
|
||||
}
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "\ud83d\udd25 \ud83d\udd25 \ud83d\udd25 A Free & Self-hostable Airtable Alternative",
|
||||
"html_url": "https://github.com/nocodb/nocodb",
|
||||
"language": "TypeScript",
|
||||
"stars_count": 62026,
|
||||
"forks_count": 4632,
|
||||
"stars_count": 62030,
|
||||
"forks_count": 4633,
|
||||
"open_issues_count": 592,
|
||||
"updated_at": "2026-02-17T20:03:56Z",
|
||||
"updated_at": "2026-02-18T02:31:38Z",
|
||||
"created_at": "2017-10-29T18:51:48Z",
|
||||
"clone_url": "https://github.com/nocodb/nocodb.git",
|
||||
"ssh_url": "git@github.com:nocodb/nocodb.git",
|
||||
"default_branch": "develop",
|
||||
"last_build_update": "2026-02-17T18:50:24Z"
|
||||
"last_build_update": "2026-02-18T02:34:42Z"
|
||||
}
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "Get up and running with Kimi-K2.5, GLM-5, MiniMax, DeepSeek, gpt-oss, Qwen, Gemma and other models.",
|
||||
"html_url": "https://github.com/ollama/ollama",
|
||||
"language": "Go",
|
||||
"stars_count": 162779,
|
||||
"stars_count": 162798,
|
||||
"forks_count": 14601,
|
||||
"open_issues_count": 2431,
|
||||
"updated_at": "2026-02-17T20:40:13Z",
|
||||
"open_issues_count": 2426,
|
||||
"updated_at": "2026-02-18T03:55:46Z",
|
||||
"created_at": "2023-06-26T19:39:32Z",
|
||||
"clone_url": "https://github.com/ollama/ollama.git",
|
||||
"ssh_url": "git@github.com:ollama/ollama.git",
|
||||
"default_branch": "main",
|
||||
"last_build_update": "2026-02-17T21:16:23Z"
|
||||
"last_build_update": "2026-02-18T02:18:06Z"
|
||||
}
|
||||
@@ -4,10 +4,10 @@
|
||||
"description": "Documentation that simply works",
|
||||
"html_url": "https://github.com/squidfunk/mkdocs-material",
|
||||
"language": "Python",
|
||||
"stars_count": 26063,
|
||||
"stars_count": 26068,
|
||||
"forks_count": 4044,
|
||||
"open_issues_count": 2,
|
||||
"updated_at": "2026-02-17T20:20:41Z",
|
||||
"updated_at": "2026-02-18T01:56:58Z",
|
||||
"created_at": "2016-01-28T22:09:23Z",
|
||||
"clone_url": "https://github.com/squidfunk/mkdocs-material.git",
|
||||
"ssh_url": "git@github.com:squidfunk/mkdocs-material.git",
|
||||
|
||||
@@ -6,35 +6,156 @@ icon: material/star-shooting
|
||||
|
||||
# Feature Guides
|
||||
|
||||
Changemaker Lite bundles advocacy campaigns, geographic mapping, volunteer management, media hosting, and landing pages into a single self-hosted platform.
|
||||
Changemaker Lite bundles advocacy campaigns, geographic mapping, volunteer management, media hosting, and landing pages into a single self-hosted platform. Every feature can be toggled on or off from **Settings** in the admin panel.
|
||||
|
||||
!!! warning "Under Construction"
|
||||
Detailed feature guides are being written. This page will link to in-depth documentation for each module.
|
||||
---
|
||||
|
||||
## Modules Overview
|
||||
## Advocacy Campaigns
|
||||
|
||||
### Influence (Advocacy Campaigns)
|
||||
Help supporters contact their elected representatives through email campaigns.
|
||||
|
||||
- **Campaigns** — create email advocacy campaigns targeting elected representatives
|
||||
- **Postal Code Lookup** — match constituents to their representatives via postal code
|
||||
- **Response Wall** — public wall where supporters share their stories, with admin moderation
|
||||
- **Email Queue** — BullMQ-powered async email delivery with tracking
|
||||
### How It Works
|
||||
|
||||
### Map (Locations & Canvassing)
|
||||
1. **An admin creates a campaign** — writes the email subject and body, selects which government levels to target (federal, provincial, municipal, school board), and publishes it.
|
||||
2. **A supporter visits the campaign page** — enters their postal code to look up their representatives.
|
||||
3. **The supporter sends the email** — either directly through the platform ("Send Now") or by opening it in their own email app (Gmail, Outlook, etc.).
|
||||
4. **Responses get tracked** — supporters and admins can share representative responses on the Response Wall, with upvoting and moderation.
|
||||
|
||||
- **Locations** — manage addresses with multi-provider geocoding and CSV import/export
|
||||
- **Cuts** — define polygon regions for organizing canvassing areas
|
||||
- **Shifts** — schedule volunteer shifts with public signup
|
||||
- **Canvassing** — GPS-tracked door-to-door canvassing with visit recording
|
||||
### Key Features
|
||||
|
||||
### Media Manager
|
||||
- **Postal code lookup** — powered by the Represent API, returns representatives at all government levels
|
||||
- **Two send methods** — server-sent SMTP (tracked) or mailto link (opens user's email app)
|
||||
- **Email editing** — optionally let supporters personalize the email before sending
|
||||
- **Response Wall** — public wall where people share how their representatives responded, with moderation and verification
|
||||
- **Campaign stats** — track emails sent, responses received, and upvotes
|
||||
- **Featured campaigns** — highlight important campaigns on the public listing page
|
||||
|
||||
- **Video Library** — upload, organize, and manage campaign videos
|
||||
- **Public Gallery** — share videos publicly with analytics tracking
|
||||
- **Scheduled Publishing** — automate video publish/unpublish with timezone support
|
||||
### Admin Routes
|
||||
|
||||
### Pages & Templates
|
||||
- `/app/campaigns` — create, edit, and manage campaigns
|
||||
- `/app/responses` — moderate submitted responses
|
||||
- `/app/email-queue` — monitor outgoing email delivery
|
||||
|
||||
- **Landing Pages** — drag-and-drop page builder (GrapesJS)
|
||||
- **Email Templates** — reusable email templates with variable substitution
|
||||
- **MkDocs Export** — export pages to the documentation site
|
||||
### Public Routes
|
||||
|
||||
- `/campaigns` — browse active campaigns
|
||||
- `/campaign/:slug` — take action on a specific campaign
|
||||
- `/campaign/:slug/responses` — view the response wall
|
||||
|
||||
---
|
||||
|
||||
## Map & Canvassing
|
||||
|
||||
Manage locations, organize canvassing territories, and coordinate volunteer door-to-door outreach.
|
||||
|
||||
### Locations
|
||||
|
||||
Import addresses via CSV or the Canadian NAR (National Address Register) dataset. Each location can be geocoded using multiple providers (Nominatim, ArcGIS, Photon, Mapbox, and more). Locations appear as color-coded markers on admin and public maps.
|
||||
|
||||
### Areas (Cuts)
|
||||
|
||||
Draw polygon regions on the map to define canvassing territories. Areas help organize locations into manageable chunks for volunteers. Each area shows stats like total addresses, visit counts, and coverage percentage.
|
||||
|
||||
### Shifts
|
||||
|
||||
Schedule volunteer time slots and let people sign up through a public page. Shifts can be linked to specific areas so volunteers know where they'll be canvassing.
|
||||
|
||||
### Canvassing
|
||||
|
||||
The volunteer canvass map is a full-screen GPS-tracked experience:
|
||||
|
||||
- **Walking routes** — the system generates an efficient route through assigned locations
|
||||
- **Visit recording** — tap a marker, record the outcome (not home, supportive, opposed, etc.)
|
||||
- **GPS tracking** — real-time position shown on the map
|
||||
- **Cluster markers** — addresses grouped intelligently for performance at scale
|
||||
|
||||
### Admin Routes
|
||||
|
||||
- `/app/map` — manage locations (CRUD, CSV import/export, geocoding)
|
||||
- `/app/map/cuts` — draw and manage canvassing areas
|
||||
- `/app/map/shifts` — schedule shifts and view signups
|
||||
- `/app/map/canvass` — canvass dashboard (stats, activity feed, leaderboard)
|
||||
|
||||
### Public Routes
|
||||
|
||||
- `/map` — public map view (address locations only, no canvass data)
|
||||
- `/shifts` — sign up for volunteer shifts
|
||||
|
||||
### Volunteer Routes
|
||||
|
||||
- `/volunteer` — full-screen canvass map with GPS and visit recording
|
||||
- `/volunteer/shifts` — view assigned shifts
|
||||
- `/volunteer/activity` — visit history and outcome breakdown
|
||||
- `/volunteer/routes` — past canvassing routes
|
||||
|
||||
---
|
||||
|
||||
## Media Manager
|
||||
|
||||
Upload, organize, and share campaign videos with built-in analytics.
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Video upload** — drag-and-drop upload with automatic metadata extraction (duration, dimensions, quality)
|
||||
- **Public gallery** — shareable video gallery at `/gallery` with category browsing
|
||||
- **Analytics** — view counts, watch time, completion rates, and traffic sources (GDPR-compliant)
|
||||
- **Scheduled publishing** — automate publish/unpublish with timezone support and a calendar view
|
||||
- **Curated playlists** — organize videos into shareable collections
|
||||
- **Quick actions** — keyboard shortcuts (E/P/A/S) for edit, preview, analytics, schedule
|
||||
|
||||
### Admin Routes
|
||||
|
||||
- `/app/media/library` — upload and manage videos
|
||||
- `/app/media/curated` — manage playlists
|
||||
- `/app/media/jobs` — monitor processing jobs
|
||||
|
||||
### Public Routes
|
||||
|
||||
- `/gallery` — browse the public video gallery
|
||||
- `/gallery/watch/:id` — watch a specific video
|
||||
|
||||
---
|
||||
|
||||
## Landing Pages
|
||||
|
||||
Build campaign microsites with a drag-and-drop visual editor.
|
||||
|
||||
### How It Works
|
||||
|
||||
1. Create a new page from the admin panel
|
||||
2. Open the GrapesJS visual editor — drag blocks, edit text, adjust styles
|
||||
3. Save and publish — the page goes live at `/p/:slug`
|
||||
4. Optionally export to MkDocs for inclusion in the documentation site
|
||||
|
||||
### Admin Routes
|
||||
|
||||
- `/app/pages` — list and manage landing pages
|
||||
- `/app/pages/:id/edit` — full-screen GrapesJS editor
|
||||
|
||||
### Public Routes
|
||||
|
||||
- `/p/:slug` — view a published landing page
|
||||
|
||||
---
|
||||
|
||||
## Newsletter (Listmonk)
|
||||
|
||||
Integrated with [Listmonk](https://listmonk.app/) for opt-in mailing lists and newsletter campaigns.
|
||||
|
||||
### Sync
|
||||
|
||||
When enabled (`LISTMONK_SYNC_ENABLED=true`), the platform syncs shift participants, location contacts, and user accounts to Listmonk subscriber lists. Sync is triggered manually from the admin panel.
|
||||
|
||||
### Admin Routes
|
||||
|
||||
- `/app/listmonk` (sidebar: "Newsletter") — sync status, subscriber counts, and manual sync trigger
|
||||
|
||||
---
|
||||
|
||||
## Email Templates
|
||||
|
||||
Create reusable email templates with variable substitution for campaign communications.
|
||||
|
||||
### Admin Routes
|
||||
|
||||
- `/app/email-templates` — create and manage email templates with a visual editor
|
||||
|
||||
@@ -6,21 +6,102 @@ icon: material/walk
|
||||
|
||||
# Volunteer Guide
|
||||
|
||||
This guide helps campaign volunteers get started with shifts, canvassing, and activity tracking.
|
||||
Welcome! This guide walks you through everything you need to know as a campaign volunteer — from signing up for your first shift to canvassing door-to-door with the GPS map.
|
||||
|
||||
!!! warning "Under Construction"
|
||||
The volunteer guide is being written. Check back soon.
|
||||
---
|
||||
|
||||
## Getting Started as a Volunteer
|
||||
## Getting Started
|
||||
|
||||
1. **Sign up for a shift** — visit the public shifts page and register for available time slots
|
||||
2. **Log in** — use the credentials from your confirmation email
|
||||
3. **View assignments** — see your assigned cuts and shifts in the volunteer portal
|
||||
4. **Start canvassing** — open the full-screen map, start a session, and record visits
|
||||
### 1. Sign Up for a Shift
|
||||
|
||||
## Volunteer Portal Features
|
||||
Visit the **Shifts** page (your organizer will share the link, or find it at `/shifts`). Browse available time slots, pick one that works for you, and fill in your name and email. You'll receive a confirmation email with your login credentials.
|
||||
|
||||
- **Assignments** — see your upcoming shifts and assigned canvassing areas
|
||||
- **Canvass Map** — GPS-tracked map with walking routes and visit recording
|
||||
- **Activity Log** — review your visit history and outcome breakdowns
|
||||
- **Routes** — view past canvassing routes
|
||||
### 2. Log In
|
||||
|
||||
Go to the login page and sign in with the email and password from your confirmation. If you already have an account, just sign in normally.
|
||||
|
||||
### 3. Open the Volunteer Portal
|
||||
|
||||
After logging in, you'll land on the **volunteer map** — a full-screen view of your assigned canvassing area. This is your home base.
|
||||
|
||||
---
|
||||
|
||||
## The Volunteer Map
|
||||
|
||||
The volunteer map is your main tool for canvassing. It shows all the addresses in your assigned area and tracks your position with GPS.
|
||||
|
||||
### What You See
|
||||
|
||||
- **Colored markers** — each marker is an address. Colors indicate the outcome of the last visit (green = supportive, red = opposed, grey = not yet visited, etc.)
|
||||
- **Clusters** — when zoomed out, markers group together and show the number of addresses in that area. Tap a cluster to zoom in.
|
||||
- **Blue dot** — your current GPS position
|
||||
- **Walking route** — a suggested path through the addresses (dotted line)
|
||||
|
||||
### Recording a Visit
|
||||
|
||||
1. **Tap a marker** to select an address
|
||||
2. A **bottom panel** slides up showing the address details
|
||||
3. Tap **Record Visit** to log what happened:
|
||||
- **Not Home** — nobody answered
|
||||
- **Supportive** — positive interaction
|
||||
- **Opposed** — not supportive
|
||||
- **Undecided** — hasn't made up their mind
|
||||
- **Moved** — no longer lives there
|
||||
- **Refused** — declined to talk
|
||||
4. Optionally add a **note** about the visit
|
||||
5. Tap **Save** — the marker color updates immediately
|
||||
|
||||
### Tips for Canvassing
|
||||
|
||||
- **Start a session** before you begin knocking on doors — this tracks your route and time
|
||||
- **End your session** when you're done for the day
|
||||
- The map works offline for basic viewing, but you need a connection to save visits
|
||||
- If GPS is inaccurate, you can manually tap the correct marker on the map
|
||||
|
||||
---
|
||||
|
||||
## Your Shifts
|
||||
|
||||
Visit **Shifts** in the bottom navigation to see your upcoming and past shifts. Each shift shows:
|
||||
|
||||
- Date and time
|
||||
- Assigned area (if linked)
|
||||
- A button to open the canvass map for that area
|
||||
|
||||
---
|
||||
|
||||
## Activity Log
|
||||
|
||||
The **Activity** tab shows your complete visit history:
|
||||
|
||||
- **Outcome breakdown** — pie chart of your visit outcomes
|
||||
- **Visit list** — each visit with address, outcome, time, and any notes
|
||||
- **Stats** — total visits, addresses covered, and sessions completed
|
||||
|
||||
---
|
||||
|
||||
## Routes
|
||||
|
||||
The **Routes** tab shows your past canvassing routes on a map. This helps you see which areas you've covered and plan your next outing.
|
||||
|
||||
---
|
||||
|
||||
## Browsing Public Pages
|
||||
|
||||
Tap your name/avatar in the header and select **Browse Site** to visit the public pages — campaigns, the public map, and shift signups. This is useful for sharing links with friends or checking campaign progress.
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: I can't find my assigned area on the map.**
|
||||
A: Make sure your shift has an area assigned. Check with your organizer if nothing appears.
|
||||
|
||||
**Q: My GPS isn't working.**
|
||||
A: Make sure you've allowed location access in your browser. Try moving to a window or stepping outside for better signal.
|
||||
|
||||
**Q: I accidentally recorded the wrong outcome.**
|
||||
A: Visit the same address again and record the correct outcome. The most recent visit is what counts.
|
||||
|
||||
**Q: How do I sign up for more shifts?**
|
||||
A: Visit the public shifts page (ask your organizer for the link, or go to `/shifts`).
|
||||
|
||||
@@ -23,6 +23,8 @@ def on_config(config: Dict[str, Any]) -> Dict[str, Any]:
|
||||
# Read environment variables (with fallbacks)
|
||||
media_api_url = os.environ.get('MEDIA_API_PUBLIC_URL', 'http://localhost:4100')
|
||||
media_api_port = os.environ.get('MEDIA_API_PORT', '4100')
|
||||
api_url = os.environ.get('API_URL', '')
|
||||
api_port = os.environ.get('API_PORT', '4000')
|
||||
admin_port = os.environ.get('ADMIN_PORT', '3000')
|
||||
admin_url = os.environ.get('ADMIN_URL', '')
|
||||
base_domain = os.environ.get('BASE_DOMAIN', '')
|
||||
@@ -41,6 +43,12 @@ def on_config(config: Dict[str, Any]) -> Dict[str, Any]:
|
||||
if is_docker_hostname(media_api_url):
|
||||
media_api_url = f'http://localhost:{media_api_port}'
|
||||
|
||||
# Resolve payment API URL — must be browser-accessible
|
||||
if api_url and not is_docker_hostname(api_url):
|
||||
payment_api_url = api_url
|
||||
else:
|
||||
payment_api_url = f'http://localhost:{api_port}'
|
||||
|
||||
# Resolve public URL (admin app)
|
||||
if admin_url and not is_docker_hostname(admin_url) and 'localhost' not in admin_url:
|
||||
# Production domain — use as-is
|
||||
@@ -49,18 +57,25 @@ def on_config(config: Dict[str, Any]) -> Dict[str, Any]:
|
||||
# Dev: use ADMIN_PORT (most reliable source of truth)
|
||||
public_url = f'http://localhost:{admin_port}'
|
||||
|
||||
# App URL (for payment fallback links, same as public_url)
|
||||
app_url = public_url
|
||||
|
||||
# Create inline JavaScript with config
|
||||
config_script = f"""
|
||||
// Auto-generated video player configuration from environment variables
|
||||
// Auto-generated configuration from environment variables
|
||||
// Generated by mkdocs/docs/hooks/env_config_hook.py
|
||||
(function() {{
|
||||
window.MEDIA_API_URL = '{media_api_url}';
|
||||
window.PUBLIC_URL = '{public_url}';
|
||||
window.PAYMENT_API_URL = '{payment_api_url}';
|
||||
window.APP_URL = '{app_url}';
|
||||
window.VIDEO_PLAYER_DEBUG = {str(os.environ.get('VIDEO_PLAYER_DEBUG', 'false')).lower()};
|
||||
|
||||
console.log('[Video Config] Loaded from environment:', {{
|
||||
console.log('[EnvConfig] Loaded from environment:', {{
|
||||
mediaApiUrl: window.MEDIA_API_URL,
|
||||
publicUrl: window.PUBLIC_URL,
|
||||
paymentApiUrl: window.PAYMENT_API_URL,
|
||||
appUrl: window.APP_URL,
|
||||
debug: window.VIDEO_PLAYER_DEBUG
|
||||
}});
|
||||
}})();
|
||||
@@ -89,16 +104,35 @@ def on_config(config: Dict[str, Any]) -> Dict[str, Any]:
|
||||
if existing_content != config_script:
|
||||
with open(env_config_file, 'w') as f:
|
||||
f.write(config_script)
|
||||
logger.info(f"✓ Generated video config: {env_config_file}")
|
||||
logger.info(f"✓ Generated env config: {env_config_file}")
|
||||
logger.info(f" MEDIA_API_URL: {media_api_url}")
|
||||
logger.info(f" PUBLIC_URL: {public_url}")
|
||||
logger.info(f" PAYMENT_API_URL: {payment_api_url}")
|
||||
logger.info(f" APP_URL: {app_url}")
|
||||
else:
|
||||
logger.info(f"✓ Video config unchanged, skipping write")
|
||||
logger.info(f"✓ Env config unchanged, skipping write")
|
||||
|
||||
# Insert at the beginning of extra_javascript list
|
||||
if env_config_path not in config['extra_javascript']:
|
||||
config['extra_javascript'].insert(0, env_config_path)
|
||||
|
||||
# Inject public_url and admin_port into config.extra so Jinja2 templates
|
||||
# can use {{ config.extra.public_url }} and {{ config.extra.admin_port }}
|
||||
# for cross-site navigation links with client-side URL resolution
|
||||
if 'extra' not in config:
|
||||
config['extra'] = {}
|
||||
config['extra']['public_url'] = public_url
|
||||
config['extra']['admin_port'] = int(admin_port)
|
||||
config['extra']['payment_api_url'] = payment_api_url
|
||||
config['extra']['app_url'] = app_url
|
||||
# API URL for docs analytics tracking (browser-accessible)
|
||||
config['extra']['api_url'] = payment_api_url # Same resolved URL
|
||||
logger.info(f" Injected config.extra.public_url: {public_url}")
|
||||
logger.info(f" Injected config.extra.admin_port: {admin_port}")
|
||||
logger.info(f" Injected config.extra.payment_api_url: {payment_api_url}")
|
||||
logger.info(f" Injected config.extra.app_url: {app_url}")
|
||||
logger.info(f" Injected config.extra.api_url: {payment_api_url}")
|
||||
|
||||
return config
|
||||
|
||||
def on_pre_build(config: Dict[str, Any]) -> None:
|
||||
@@ -106,4 +140,4 @@ def on_pre_build(config: Dict[str, Any]) -> None:
|
||||
Hook that runs before build starts.
|
||||
Ensures config is regenerated on each build.
|
||||
"""
|
||||
logger.info("Video player configuration will be generated from environment variables")
|
||||
logger.info("Environment configuration will be generated from environment variables")
|
||||
|
||||
7
mkdocs/docs/main.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
template: main.html
|
||||
hide:
|
||||
- navigation
|
||||
- toc
|
||||
title: "main"
|
||||
---
|
||||
74
mkdocs/docs/overrides/header-config.json
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"enabled": true,
|
||||
"items": [
|
||||
{
|
||||
"id": "campaigns",
|
||||
"label": "Campaigns",
|
||||
"path": "/campaigns",
|
||||
"icon": "campaign",
|
||||
"enabled": true,
|
||||
"order": 0,
|
||||
"type": "builtin"
|
||||
},
|
||||
{
|
||||
"id": "map",
|
||||
"label": "Map",
|
||||
"path": "/map",
|
||||
"icon": "map",
|
||||
"enabled": true,
|
||||
"order": 1,
|
||||
"type": "builtin"
|
||||
},
|
||||
{
|
||||
"id": "shifts",
|
||||
"label": "Volunteer",
|
||||
"path": "/shifts",
|
||||
"icon": "groups",
|
||||
"enabled": true,
|
||||
"order": 2,
|
||||
"type": "builtin"
|
||||
},
|
||||
{
|
||||
"id": "gallery",
|
||||
"label": "Gallery",
|
||||
"path": "/gallery",
|
||||
"icon": "play_circle",
|
||||
"enabled": true,
|
||||
"order": 3,
|
||||
"type": "builtin"
|
||||
},
|
||||
{
|
||||
"id": "responses",
|
||||
"label": "Responses",
|
||||
"path": "/responses",
|
||||
"icon": "forum",
|
||||
"enabled": true,
|
||||
"order": 4,
|
||||
"type": "builtin"
|
||||
},
|
||||
{
|
||||
"id": "donate",
|
||||
"label": "Donate",
|
||||
"path": "/donate",
|
||||
"icon": "favorite",
|
||||
"enabled": true,
|
||||
"order": 5,
|
||||
"type": "builtin"
|
||||
},
|
||||
{
|
||||
"id": "login",
|
||||
"label": "Sign In",
|
||||
"path": "/login",
|
||||
"icon": "login",
|
||||
"enabled": true,
|
||||
"order": 6,
|
||||
"type": "builtin"
|
||||
}
|
||||
],
|
||||
"style": {
|
||||
"backgroundColor": "#6f42c1",
|
||||
"textColor": "#ffffff",
|
||||
"hoverColor": "rgba(255,255,255,0.15)",
|
||||
"height": "40px"
|
||||
}
|
||||
}
|
||||
125
mkdocs/docs/overrides/main.html
Normal file
@@ -0,0 +1,125 @@
|
||||
{# Auto-generated by Changemaker Lite Header Builder — do not edit manually #}
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block announce %}
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<nav class="cm-header-nav" role="navigation" aria-label="Application">
|
||||
<div class="cm-header-nav__inner">
|
||||
<a href="#" data-path="/campaigns" class="cm-header-nav__link">
|
||||
<span class="material-icons">campaign</span>
|
||||
<span class="cm-header-nav__label">Campaigns</span>
|
||||
</a>
|
||||
<a href="#" data-path="/map" class="cm-header-nav__link">
|
||||
<span class="material-icons">map</span>
|
||||
<span class="cm-header-nav__label">Map</span>
|
||||
</a>
|
||||
<a href="#" data-path="/shifts" class="cm-header-nav__link">
|
||||
<span class="material-icons">groups</span>
|
||||
<span class="cm-header-nav__label">Volunteer</span>
|
||||
</a>
|
||||
<a href="#" data-path="/gallery" class="cm-header-nav__link">
|
||||
<span class="material-icons">play_circle</span>
|
||||
<span class="cm-header-nav__label">Gallery</span>
|
||||
</a>
|
||||
<a href="#" data-path="/responses" class="cm-header-nav__link">
|
||||
<span class="material-icons">forum</span>
|
||||
<span class="cm-header-nav__label">Responses</span>
|
||||
</a>
|
||||
<a href="#" data-path="/donate" class="cm-header-nav__link">
|
||||
<span class="material-icons">favorite</span>
|
||||
<span class="cm-header-nav__label">Donate</span>
|
||||
</a>
|
||||
<a href="#" data-path="/login" class="cm-header-nav__link">
|
||||
<span class="material-icons">login</span>
|
||||
<span class="cm-header-nav__label">Sign In</span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<script>
|
||||
// Resolve nav link hrefs based on the current browser hostname.
|
||||
// localhost → http://localhost:{ADMIN_PORT}
|
||||
// subdomain.example.org → {proto}://app.example.org
|
||||
(function() {
|
||||
var h = location.hostname;
|
||||
var base;
|
||||
if (h === 'localhost' || h === '127.0.0.1') {
|
||||
base = location.protocol + '//localhost:' + ({{ config.extra.admin_port }} || 3000);
|
||||
} else {
|
||||
var parts = h.split('.');
|
||||
if (parts.length >= 3) { parts[0] = 'app'; }
|
||||
else { parts.unshift('app'); }
|
||||
base = location.protocol + '//' + parts.join('.');
|
||||
}
|
||||
var links = document.querySelectorAll('.cm-header-nav__link[data-path]');
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
links[i].setAttribute('href', base + links[i].getAttribute('data-path'));
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<style>
|
||||
/* Override MkDocs Material announce bar container */
|
||||
.md-banner {
|
||||
background: #6f42c1 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
/* Hide the dismiss (X) button that Material adds for announce.dismiss */
|
||||
.md-banner__button {
|
||||
display: none !important;
|
||||
}
|
||||
.cm-header-nav {
|
||||
background: #6f42c1;
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 24px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
z-index: 10;
|
||||
}
|
||||
.cm-header-nav__inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
max-width: 1400px;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
flex-wrap: nowrap;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.cm-header-nav__link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 16px;
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
border-radius: 6px;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
transition: background 0.15s, transform 0.1s;
|
||||
white-space: nowrap;
|
||||
line-height: 1;
|
||||
}
|
||||
.cm-header-nav__link:hover {
|
||||
background: rgba(255,255,255,0.15);
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.cm-header-nav__link:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.cm-header-nav__link .material-icons {
|
||||
font-size: 16px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.cm-header-nav { padding: 0 8px; }
|
||||
.cm-header-nav__label { display: none; }
|
||||
.cm-header-nav__link { padding: 8px 10px; }
|
||||
.cm-header-nav__inner { gap: 4px; }
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,55 @@
|
||||
<script>
|
||||
(function () {
|
||||
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
|
||||
var apiUrl = "{{ config.extra.api_url }}";
|
||||
if (!apiUrl) return;
|
||||
|
||||
var trackUrl = apiUrl + "/api/docs-analytics/track";
|
||||
|
||||
// Anonymous session UUID (sessionStorage — dies with tab close, no cookies)
|
||||
function getSessionHash() {
|
||||
var key = "__docs_sh";
|
||||
var hash = sessionStorage.getItem(key);
|
||||
if (!hash) {
|
||||
hash = crypto.randomUUID
|
||||
? crypto.randomUUID()
|
||||
: "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
|
||||
var r = (Math.random() * 16) | 0;
|
||||
return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
|
||||
});
|
||||
sessionStorage.setItem(key, hash);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
function trackPageView(path) {
|
||||
var payload = JSON.stringify({
|
||||
path: path,
|
||||
referrer: document.referrer || undefined,
|
||||
sessionHash: getSessionHash(),
|
||||
});
|
||||
|
||||
// Prefer sendBeacon for reliability (works during tab close)
|
||||
if (navigator.sendBeacon) {
|
||||
navigator.sendBeacon(trackUrl, new Blob([payload], { type: "application/json" }));
|
||||
} else {
|
||||
fetch(trackUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: payload,
|
||||
keepalive: true,
|
||||
}).catch(function () {});
|
||||
}
|
||||
}
|
||||
|
||||
// Track initial page load
|
||||
trackPageView(location.pathname);
|
||||
|
||||
// Subscribe to Material's SPA navigation observable (instant loading)
|
||||
if (typeof document$ !== "undefined") {
|
||||
document$.subscribe(function () {
|
||||
trackPageView(location.pathname);
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
7
mkdocs/docs/partials/integrations/analytics/custom.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
template: partials/integrations/analytics/custom.html
|
||||
hide:
|
||||
- navigation
|
||||
- toc
|
||||
title: "custom"
|
||||
---
|
||||
@@ -3,6 +3,179 @@
|
||||
Testing page.
|
||||
|
||||
|
||||
<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;">
|
||||
|
||||
@@ -73,6 +73,7 @@ extra_css:
|
||||
- stylesheets/extra.css
|
||||
- stylesheets/home.css
|
||||
- assets/css/video-player.css
|
||||
- assets/css/payment-widgets.css
|
||||
|
||||
extra_javascript:
|
||||
- javascripts/home.js
|
||||
@@ -81,6 +82,7 @@ extra_javascript:
|
||||
# env-config.js is auto-generated by hooks/env_config_hook.py
|
||||
- assets/js/env-config.js
|
||||
- assets/js/video-player.js
|
||||
- assets/js/payment-widgets.js
|
||||
|
||||
hooks:
|
||||
- docs/hooks/repo_widget_hook.py
|
||||
@@ -130,6 +132,8 @@ markdown_extensions:
|
||||
|
||||
# Extra configuration
|
||||
extra:
|
||||
analytics:
|
||||
provider: custom
|
||||
generator: false
|
||||
social:
|
||||
- icon: fontawesome/brands/github
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en" class="no-js">
|
||||
<head>
|
||||
@@ -82,6 +83,142 @@
|
||||
</div>
|
||||
<div data-md-component="announce">
|
||||
|
||||
<aside class="md-banner">
|
||||
<div class="md-banner__inner md-grid md-typeset">
|
||||
|
||||
<button class="md-banner__button md-icon" aria-label="Don't show this again">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
|
||||
</button>
|
||||
|
||||
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<nav class="cm-header-nav" role="navigation" aria-label="Application">
|
||||
<div class="cm-header-nav__inner">
|
||||
<a href="#" data-path="/campaigns" class="cm-header-nav__link">
|
||||
<span class="material-icons">campaign</span>
|
||||
<span class="cm-header-nav__label">Campaigns</span>
|
||||
</a>
|
||||
<a href="#" data-path="/map" class="cm-header-nav__link">
|
||||
<span class="material-icons">map</span>
|
||||
<span class="cm-header-nav__label">Map</span>
|
||||
</a>
|
||||
<a href="#" data-path="/shifts" class="cm-header-nav__link">
|
||||
<span class="material-icons">groups</span>
|
||||
<span class="cm-header-nav__label">Volunteer</span>
|
||||
</a>
|
||||
<a href="#" data-path="/gallery" class="cm-header-nav__link">
|
||||
<span class="material-icons">play_circle</span>
|
||||
<span class="cm-header-nav__label">Gallery</span>
|
||||
</a>
|
||||
<a href="#" data-path="/responses" class="cm-header-nav__link">
|
||||
<span class="material-icons">forum</span>
|
||||
<span class="cm-header-nav__label">Responses</span>
|
||||
</a>
|
||||
<a href="#" data-path="/donate" class="cm-header-nav__link">
|
||||
<span class="material-icons">favorite</span>
|
||||
<span class="cm-header-nav__label">Donate</span>
|
||||
</a>
|
||||
<a href="#" data-path="/login" class="cm-header-nav__link">
|
||||
<span class="material-icons">login</span>
|
||||
<span class="cm-header-nav__label">Sign In</span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<script>
|
||||
// Resolve nav link hrefs based on the current browser hostname.
|
||||
// localhost → http://localhost:{ADMIN_PORT}
|
||||
// subdomain.example.org → {proto}://app.example.org
|
||||
(function() {
|
||||
var h = location.hostname;
|
||||
var base;
|
||||
if (h === 'localhost' || h === '127.0.0.1') {
|
||||
base = location.protocol + '//localhost:' + (3002 || 3000);
|
||||
} else {
|
||||
var parts = h.split('.');
|
||||
if (parts.length >= 3) { parts[0] = 'app'; }
|
||||
else { parts.unshift('app'); }
|
||||
base = location.protocol + '//' + parts.join('.');
|
||||
}
|
||||
var links = document.querySelectorAll('.cm-header-nav__link[data-path]');
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
links[i].setAttribute('href', base + links[i].getAttribute('data-path'));
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<style>
|
||||
/* Override MkDocs Material announce bar container */
|
||||
.md-banner {
|
||||
background: #6f42c1 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
/* Hide the dismiss (X) button that Material adds for announce.dismiss */
|
||||
.md-banner__button {
|
||||
display: none !important;
|
||||
}
|
||||
.cm-header-nav {
|
||||
background: #6f42c1;
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 24px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
z-index: 10;
|
||||
}
|
||||
.cm-header-nav__inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
max-width: 1400px;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
flex-wrap: nowrap;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.cm-header-nav__link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 16px;
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
border-radius: 6px;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
transition: background 0.15s, transform 0.1s;
|
||||
white-space: nowrap;
|
||||
line-height: 1;
|
||||
}
|
||||
.cm-header-nav__link:hover {
|
||||
background: rgba(255,255,255,0.15);
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.cm-header-nav__link:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.cm-header-nav__link .material-icons {
|
||||
font-size: 16px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.cm-header-nav { padding: 0 8px; }
|
||||
.cm-header-nav__label { display: none; }
|
||||
.cm-header-nav__link { padding: 8px 10px; }
|
||||
.cm-header-nav__inner { gap: 4px; }
|
||||
}
|
||||
</style>
|
||||
|
||||
</div>
|
||||
|
||||
<script>var el=document.querySelector("[data-md-component=announce]");if(el){var content=el.querySelector(".md-typeset");__md_hash(content.innerHTML)===__md_get("__announce")&&(el.hidden=!0)}</script>
|
||||
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -242,6 +379,30 @@
|
||||
|
||||
|
||||
|
||||
<li class="md-tabs__item">
|
||||
<a href="/docs/" class="md-tabs__link">
|
||||
|
||||
|
||||
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 21.5c-1.35-.85-3.8-1.5-5.5-1.5-1.65 0-3.35.3-4.75 1.05-.1.05-.15.05-.25.05-.25 0-.5-.25-.5-.5V6c.6-.45 1.25-.75 2-1 1.11-.35 2.33-.5 3.5-.5 1.95 0 4.05.4 5.5 1.5 1.45-1.1 3.55-1.5 5.5-1.5 1.17 0 2.39.15 3.5.5.75.25 1.4.55 2 1v14.6c0 .25-.25.5-.5.5-.1 0-.15 0-.25-.05-1.4-.75-3.1-1.05-4.75-1.05-1.7 0-4.15.65-5.5 1.5M12 8v11.5c1.35-.85 3.8-1.5 5.5-1.5 1.2 0 2.4.15 3.5.5V7c-1.1-.35-2.3-.5-3.5-.5-1.7 0-4.15.65-5.5 1.5m1 3.5c1.11-.68 2.6-1 4.5-1 .91 0 1.76.09 2.5.28V9.23c-.87-.15-1.71-.23-2.5-.23q-2.655 0-4.5.84zm4.5.17c-1.71 0-3.21.26-4.5.79v1.69c1.11-.65 2.6-.99 4.5-.99 1.04 0 1.88.08 2.5.24v-1.5c-.87-.16-1.71-.23-2.5-.23m2.5 2.9c-.87-.16-1.71-.24-2.5-.24-1.83 0-3.33.27-4.5.8v1.69c1.11-.66 2.6-.99 4.5-.99 1.04 0 1.88.08 2.5.24z"/></svg>
|
||||
|
||||
|
||||
Docs
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-tabs__item">
|
||||
<a href="/blog/" class="md-tabs__link">
|
||||
|
||||
@@ -349,12 +510,85 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="/docs/" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
Docs
|
||||
|
||||
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
|
||||
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -501,7 +735,7 @@
|
||||
|
||||
|
||||
|
||||
<script id="__config" type="application/json">{"annotate": null, "base": "/", "features": ["announce.dismiss", "content.action.edit", "content.action.view", "content.code.annotate", "content.code.copy", "content.tooltips", "navigation.expand", "navigation.footer", "navigation.indexes", "navigation.path", "navigation.prune", "navigation.sections", "navigation.tabs", "navigation.tabs.sticky", "navigation.top", "navigation.tracking", "search.highlight", "search.share", "search.suggest", "toc.follow"], "search": "/assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
|
||||
<script id="__config" type="application/json">{"annotate": null, "base": "/", "features": ["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"], "search": "/assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
|
||||
|
||||
|
||||
<script src="/assets/javascripts/bundle.79ae519e.min.js"></script>
|
||||
|
||||
@@ -102,14 +102,20 @@
|
||||
/* Responsive video containers */
|
||||
@media (max-width: 768px) {
|
||||
.video-block,
|
||||
.advanced-video-container {
|
||||
.advanced-video-container,
|
||||
.video-card-block {
|
||||
margin: 1.5rem auto;
|
||||
}
|
||||
|
||||
.video-block video,
|
||||
.advanced-video-container video {
|
||||
.advanced-video-container video,
|
||||
.video-card-player video {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.video-card-block {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Video placeholder (shown during GrapesJS export before hydration) */
|
||||
@@ -138,9 +144,45 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Video card block (YouTube-style preview card) */
|
||||
.video-card-block {
|
||||
margin: 2rem auto;
|
||||
max-width: 480px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
.video-card-block:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.video-card-block a {
|
||||
text-decoration: none !important;
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
/* Video card gallery button */
|
||||
.video-card-gallery-btn:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
/* Video card inline player (after clicking thumbnail) */
|
||||
.video-card-player video {
|
||||
width: 100%;
|
||||
display: block;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.video-card-player video:focus {
|
||||
outline: 2px solid var(--md-primary-fg-color);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
|
||||
/* Ensure videos respect content width in Material theme */
|
||||
.md-content .video-block,
|
||||
.md-content .advanced-video-container {
|
||||
.md-content .advanced-video-container,
|
||||
.md-content .video-card-block {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
|
||||
BIN
mkdocs/site/assets/images/social/docs/admin/index.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
mkdocs/site/assets/images/social/docs/api/index.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
mkdocs/site/assets/images/social/docs/architecture/index.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
mkdocs/site/assets/images/social/docs/deployment/index.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
mkdocs/site/assets/images/social/docs/features/index.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 72 KiB |
BIN
mkdocs/site/assets/images/social/docs/getting-started/index.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
mkdocs/site/assets/images/social/docs/index.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
mkdocs/site/assets/images/social/docs/services/index.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
mkdocs/site/assets/images/social/docs/troubleshooting/index.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
mkdocs/site/assets/images/social/docs/volunteer/index.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
mkdocs/site/assets/images/social/main.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
@@ -2,7 +2,7 @@
|
||||
// Generated by mkdocs/docs/hooks/env_config_hook.py
|
||||
(function() {
|
||||
window.MEDIA_API_URL = 'http://localhost:4100';
|
||||
window.PUBLIC_URL = 'http://localhost:3000';
|
||||
window.PUBLIC_URL = 'http://localhost:3002';
|
||||
window.VIDEO_PLAYER_DEBUG = false;
|
||||
|
||||
console.log('[Video Config] Loaded from environment:', {
|
||||
|
||||
@@ -189,14 +189,202 @@
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
/**
|
||||
* Format view count (e.g. 1200 → "1.2K views")
|
||||
*/
|
||||
function formatViewCount(count) {
|
||||
if (!count || count <= 0) return '0 views';
|
||||
if (count === 1) return '1 view';
|
||||
if (count < 1000) return count + ' views';
|
||||
if (count < 1000000) return (count / 1000).toFixed(1).replace(/\.0$/, '') + 'K views';
|
||||
return (count / 1000000).toFixed(1).replace(/\.0$/, '') + 'M views';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the gallery URL for a video.
|
||||
*/
|
||||
function getGalleryUrl(videoId) {
|
||||
return PUBLIC_URL + '/gallery?expanded=' + videoId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate a video card block:
|
||||
* 1. Fix thumbnail URL using MEDIA_API_URL
|
||||
* 2. Fetch live metadata (views, title)
|
||||
* 3. Clicking thumbnail plays video inline
|
||||
* 4. Replace "Watch →" with Gallery link
|
||||
*/
|
||||
async function hydrateVideoCard(cardElement) {
|
||||
var videoId = parseInt(cardElement.dataset.videoId, 10);
|
||||
if (!videoId || isNaN(videoId)) return;
|
||||
|
||||
var title = cardElement.dataset.videoTitle || 'Video';
|
||||
|
||||
// 1. Fix thumbnail URL — replace placeholder/broken src with MEDIA_API_URL
|
||||
var img = cardElement.querySelector('img');
|
||||
if (img) {
|
||||
// Cache-buster avoids stale CORS-error cached responses
|
||||
var correctThumbUrl = MEDIA_API_URL + '/api/videos/' + videoId + '/thumbnail?v=' + Date.now();
|
||||
// Always replace src — data URI placeholder, Docker hostname, or stale URL
|
||||
img.src = correctThumbUrl;
|
||||
// Fallback if thumbnail fails to load — show a styled placeholder instead of broken image
|
||||
img.onerror = function() {
|
||||
img.style.display = 'none';
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Fetch live metadata to update views/title
|
||||
try {
|
||||
var metadata = await fetchVideoMetadata(videoId);
|
||||
if (metadata) {
|
||||
var viewsEl = cardElement.querySelector('[data-role="views"]');
|
||||
if (viewsEl && metadata.viewCount !== undefined) {
|
||||
viewsEl.textContent = formatViewCount(metadata.viewCount);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Non-critical — card works fine without live metadata
|
||||
}
|
||||
|
||||
// 3. Prevent the <a> tag from navigating
|
||||
var link = cardElement.querySelector('a');
|
||||
if (link) {
|
||||
link.addEventListener('click', function(e) { e.preventDefault(); });
|
||||
}
|
||||
|
||||
// 4. Fix the play button overlay SVG + set up click-to-play
|
||||
var thumbContainer = cardElement.querySelector('a > div:first-child');
|
||||
if (thumbContainer) {
|
||||
// Replace old tiny play icon with proper triangle
|
||||
var playOverlay = thumbContainer.querySelector('div:last-child');
|
||||
if (playOverlay && playOverlay.querySelector('svg')) {
|
||||
playOverlay.innerHTML = '<svg width="28" height="28" viewBox="0 0 24 24" fill="#fff" style="margin-left:3px;"><polygon points="5,3 19,12 5,21"/></svg>';
|
||||
}
|
||||
|
||||
thumbContainer.style.cursor = 'pointer';
|
||||
thumbContainer.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Don't re-create if already playing
|
||||
if (cardElement.querySelector('video')) return;
|
||||
|
||||
var streamUrl = MEDIA_API_URL + '/api/videos/' + videoId + '/stream';
|
||||
var thumbUrl = MEDIA_API_URL + '/api/videos/' + videoId + '/thumbnail';
|
||||
|
||||
// Build inline player — locked to same 16:9 aspect ratio as the card
|
||||
var playerContainer = document.createElement('div');
|
||||
playerContainer.className = 'video-card-player';
|
||||
playerContainer.style.cssText = 'max-width:480px;margin:0 auto;border-radius:12px;overflow:hidden;background:#0d1b2a;box-shadow:0 4px 12px rgba(0,0,0,0.3);';
|
||||
|
||||
// 16:9 aspect ratio wrapper prevents container resize when video loads
|
||||
var videoWrapper = document.createElement('div');
|
||||
videoWrapper.style.cssText = 'position:relative;padding-bottom:56.25%;background:#000;';
|
||||
|
||||
var video = document.createElement('video');
|
||||
video.src = streamUrl;
|
||||
video.poster = thumbUrl;
|
||||
video.controls = true;
|
||||
video.autoplay = true;
|
||||
video.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;display:block;border-radius:12px 12px 0 0;';
|
||||
video.setAttribute('title', title);
|
||||
|
||||
videoWrapper.appendChild(video);
|
||||
|
||||
var infoBar = document.createElement('div');
|
||||
infoBar.style.cssText = 'padding:10px 16px;background:#1b2838;display:flex;align-items:center;gap:8px;';
|
||||
|
||||
var titleSpan = document.createElement('span');
|
||||
titleSpan.style.cssText = 'color:#fff;font-size:14px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;';
|
||||
titleSpan.textContent = title;
|
||||
|
||||
var galleryLink = document.createElement('a');
|
||||
galleryLink.href = getGalleryUrl(videoId);
|
||||
galleryLink.target = '_blank';
|
||||
galleryLink.innerHTML = '▶ Gallery';
|
||||
galleryLink.className = 'video-card-gallery-btn';
|
||||
galleryLink.style.cssText = 'background:#9d4edd;border:none;color:#fff;font-size:12px;font-weight:600;padding:5px 12px;border-radius:4px;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:background 0.15s;text-decoration:none;display:inline-block;';
|
||||
galleryLink.title = 'Open in gallery';
|
||||
galleryLink.onmouseenter = function() { galleryLink.style.background = '#b06ce6'; };
|
||||
galleryLink.onmouseleave = function() { galleryLink.style.background = '#9d4edd'; };
|
||||
galleryLink.addEventListener('click', function(ev) {
|
||||
ev.stopPropagation();
|
||||
});
|
||||
|
||||
var closeBtn = document.createElement('button');
|
||||
closeBtn.textContent = '\u2715';
|
||||
closeBtn.style.cssText = 'background:none;border:none;color:#8899aa;font-size:16px;cursor:pointer;padding:4px;flex-shrink:0;transition:color 0.15s;';
|
||||
closeBtn.title = 'Close player';
|
||||
closeBtn.onmouseenter = function() { closeBtn.style.color = '#fff'; };
|
||||
closeBtn.onmouseleave = function() { closeBtn.style.color = '#8899aa'; };
|
||||
|
||||
infoBar.appendChild(titleSpan);
|
||||
infoBar.appendChild(galleryLink);
|
||||
infoBar.appendChild(closeBtn);
|
||||
playerContainer.appendChild(videoWrapper);
|
||||
playerContainer.appendChild(infoBar);
|
||||
|
||||
// Save original card content and replace
|
||||
var originalContent = cardElement.innerHTML;
|
||||
cardElement.innerHTML = '';
|
||||
cardElement.appendChild(playerContainer);
|
||||
|
||||
// Close button restores original card
|
||||
closeBtn.addEventListener('click', function(ev) {
|
||||
ev.stopPropagation();
|
||||
video.pause();
|
||||
video.src = '';
|
||||
cardElement.innerHTML = originalContent;
|
||||
hydrateVideoCard(cardElement);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 5. Replace "Watch →" text with a Gallery button in the card info bar
|
||||
var infoBar = cardElement.querySelector('a > div:last-child');
|
||||
if (infoBar) {
|
||||
var flexRow = infoBar.querySelector('div:last-child');
|
||||
if (flexRow) {
|
||||
var spans = flexRow.querySelectorAll('span');
|
||||
var watchSpan = null;
|
||||
for (var i = 0; i < spans.length; i++) {
|
||||
if (spans[i].textContent.indexOf('Watch') !== -1) {
|
||||
watchSpan = spans[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (watchSpan) {
|
||||
var galleryLink = document.createElement('a');
|
||||
galleryLink.href = getGalleryUrl(videoId);
|
||||
galleryLink.target = '_blank';
|
||||
galleryLink.innerHTML = '▶ Gallery';
|
||||
galleryLink.className = 'video-card-gallery-btn';
|
||||
galleryLink.style.cssText = 'background:#9d4edd;border:none;color:#fff;font-size:12px;font-weight:600;padding:5px 14px;border-radius:4px;cursor:pointer;transition:background 0.15s;text-decoration:none;display:inline-block;';
|
||||
galleryLink.title = 'Open in gallery';
|
||||
galleryLink.onmouseenter = function() { galleryLink.style.background = '#b06ce6'; };
|
||||
galleryLink.onmouseleave = function() { galleryLink.style.background = '#9d4edd'; };
|
||||
galleryLink.addEventListener('click', function(ev) {
|
||||
ev.stopPropagation(); // Don't trigger card click-to-play
|
||||
});
|
||||
watchSpan.replaceWith(galleryLink);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark as hydrated
|
||||
cardElement.dataset.hydrated = 'true';
|
||||
console.log('Hydrated video card for video ' + videoId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all video blocks on page load
|
||||
*/
|
||||
function initVideoBlocks() {
|
||||
const blocks = document.querySelectorAll('.video-block');
|
||||
console.log(`Found ${blocks.length} video block(s) to hydrate`);
|
||||
var blocks = document.querySelectorAll('.video-block');
|
||||
console.log('Found ' + blocks.length + ' video block(s) to hydrate');
|
||||
|
||||
blocks.forEach(block => {
|
||||
blocks.forEach(function(block) {
|
||||
// Skip if already rendered (has video element)
|
||||
if (block.querySelector('video')) {
|
||||
console.log('Video block already rendered, skipping');
|
||||
@@ -207,18 +395,40 @@
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all video card blocks on page load
|
||||
*/
|
||||
function initVideoCards() {
|
||||
var cards = document.querySelectorAll('.video-card-block');
|
||||
console.log('Found ' + cards.length + ' video card(s) to hydrate');
|
||||
|
||||
cards.forEach(function(card) {
|
||||
// Skip if already hydrated
|
||||
if (card.dataset.hydrated === 'true') return;
|
||||
hydrateVideoCard(card);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all video elements (blocks + cards)
|
||||
*/
|
||||
function initAll() {
|
||||
initVideoBlocks();
|
||||
initVideoCards();
|
||||
}
|
||||
|
||||
// Initialize when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initVideoBlocks);
|
||||
document.addEventListener('DOMContentLoaded', initAll);
|
||||
} else {
|
||||
initVideoBlocks();
|
||||
initAll();
|
||||
}
|
||||
|
||||
// Re-initialize on MkDocs navigation (for SPA-style navigation)
|
||||
if (typeof window.document$ !== 'undefined') {
|
||||
window.document$.subscribe(() => {
|
||||
console.log('MkDocs navigation detected, re-initializing video blocks');
|
||||
setTimeout(initVideoBlocks, 100);
|
||||
window.document$.subscribe(function() {
|
||||
console.log('MkDocs navigation detected, re-initializing video elements');
|
||||
setTimeout(initAll, 100);
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
"stars_count": 0,
|
||||
"forks_count": 0,
|
||||
"open_issues_count": 23,
|
||||
"updated_at": "2026-02-17T10:36:57-07:00",
|
||||
"updated_at": "2026-02-17T15:49:30-07:00",
|
||||
"created_at": "2025-05-28T14:54:59-06:00",
|
||||
"clone_url": "https://gitea.bnkops.com/admin/changemaker.lite.git",
|
||||
"ssh_url": "git@gitea.bnkops.com:admin/changemaker.lite.git",
|
||||
"default_branch": "main",
|
||||
"last_build_update": "2026-02-17T10:36:57-07:00"
|
||||
"last_build_update": "2026-02-17T15:49:30-07:00"
|
||||
}
|
||||
@@ -4,10 +4,10 @@
|
||||
"description": "Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.",
|
||||
"html_url": "https://github.com/anthropics/claude-code",
|
||||
"language": "Shell",
|
||||
"stars_count": 67351,
|
||||
"forks_count": 5255,
|
||||
"open_issues_count": 6188,
|
||||
"updated_at": "2026-02-17T21:23:40Z",
|
||||
"stars_count": 67411,
|
||||
"forks_count": 5260,
|
||||
"open_issues_count": 6231,
|
||||
"updated_at": "2026-02-18T04:18:30Z",
|
||||
"created_at": "2025-02-22T17:41:21Z",
|
||||
"clone_url": "https://github.com/anthropics/claude-code.git",
|
||||
"ssh_url": "git@github.com:anthropics/claude-code.git",
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
"description": "VS Code in the browser",
|
||||
"html_url": "https://github.com/coder/code-server",
|
||||
"language": "TypeScript",
|
||||
"stars_count": 76278,
|
||||
"forks_count": 6513,
|
||||
"stars_count": 76281,
|
||||
"forks_count": 6512,
|
||||
"open_issues_count": 176,
|
||||
"updated_at": "2026-02-17T20:24:30Z",
|
||||
"updated_at": "2026-02-18T03:57:21Z",
|
||||
"created_at": "2019-02-27T16:50:41Z",
|
||||
"clone_url": "https://github.com/coder/code-server.git",
|
||||
"ssh_url": "git@github.com:coder/code-server.git",
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "A highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations.",
|
||||
"html_url": "https://github.com/gethomepage/homepage",
|
||||
"language": "JavaScript",
|
||||
"stars_count": 28451,
|
||||
"stars_count": 28454,
|
||||
"forks_count": 1790,
|
||||
"open_issues_count": 1,
|
||||
"updated_at": "2026-02-17T19:07:13Z",
|
||||
"updated_at": "2026-02-18T03:37:12Z",
|
||||
"created_at": "2022-08-24T07:29:42Z",
|
||||
"clone_url": "https://github.com/gethomepage/homepage.git",
|
||||
"ssh_url": "git@github.com:gethomepage/homepage.git",
|
||||
"default_branch": "dev",
|
||||
"last_build_update": "2026-02-17T12:23:04Z"
|
||||
"last_build_update": "2026-02-18T00:48:06Z"
|
||||
}
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "Git with a cup of tea! Painless self-hosted all-in-one software development service, including Git hosting, code review, team collaboration, package registry and CI/CD",
|
||||
"html_url": "https://github.com/go-gitea/gitea",
|
||||
"language": "Go",
|
||||
"stars_count": 53755,
|
||||
"forks_count": 6390,
|
||||
"open_issues_count": 2835,
|
||||
"updated_at": "2026-02-17T20:31:54Z",
|
||||
"stars_count": 53756,
|
||||
"forks_count": 6393,
|
||||
"open_issues_count": 2831,
|
||||
"updated_at": "2026-02-18T04:13:31Z",
|
||||
"created_at": "2016-11-01T02:13:26Z",
|
||||
"clone_url": "https://github.com/go-gitea/gitea.git",
|
||||
"ssh_url": "git@github.com:go-gitea/gitea.git",
|
||||
"default_branch": "main",
|
||||
"last_build_update": "2026-02-17T20:32:46Z"
|
||||
"last_build_update": "2026-02-18T04:14:21Z"
|
||||
}
|
||||
@@ -4,10 +4,10 @@
|
||||
"description": "High performance, self-hosted, newsletter and mailing list manager with a modern dashboard. Single binary app.",
|
||||
"html_url": "https://github.com/knadh/listmonk",
|
||||
"language": "Go",
|
||||
"stars_count": 19074,
|
||||
"stars_count": 19075,
|
||||
"forks_count": 1926,
|
||||
"open_issues_count": 113,
|
||||
"updated_at": "2026-02-17T16:51:23Z",
|
||||
"updated_at": "2026-02-17T21:39:10Z",
|
||||
"created_at": "2019-06-26T05:08:39Z",
|
||||
"clone_url": "https://github.com/knadh/listmonk.git",
|
||||
"ssh_url": "git@github.com:knadh/listmonk.git",
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
"description": "Create & scan cute qr codes easily \ud83d\udc7e",
|
||||
"html_url": "https://github.com/lyqht/mini-qr",
|
||||
"language": "Vue",
|
||||
"stars_count": 1851,
|
||||
"stars_count": 1852,
|
||||
"forks_count": 236,
|
||||
"open_issues_count": 21,
|
||||
"updated_at": "2026-02-17T15:35:23Z",
|
||||
"updated_at": "2026-02-17T22:05:48Z",
|
||||
"created_at": "2023-04-21T14:20:14Z",
|
||||
"clone_url": "https://github.com/lyqht/mini-qr.git",
|
||||
"ssh_url": "git@github.com:lyqht/mini-qr.git",
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.",
|
||||
"html_url": "https://github.com/n8n-io/n8n",
|
||||
"language": "TypeScript",
|
||||
"stars_count": 174947,
|
||||
"forks_count": 54925,
|
||||
"open_issues_count": 1388,
|
||||
"updated_at": "2026-02-17T21:23:05Z",
|
||||
"stars_count": 174994,
|
||||
"forks_count": 54934,
|
||||
"open_issues_count": 1392,
|
||||
"updated_at": "2026-02-18T04:21:26Z",
|
||||
"created_at": "2019-06-22T09:24:21Z",
|
||||
"clone_url": "https://github.com/n8n-io/n8n.git",
|
||||
"ssh_url": "git@github.com:n8n-io/n8n.git",
|
||||
"default_branch": "master",
|
||||
"last_build_update": "2026-02-17T21:12:15Z"
|
||||
"last_build_update": "2026-02-18T01:57:27Z"
|
||||
}
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "\ud83d\udd25 \ud83d\udd25 \ud83d\udd25 A Free & Self-hostable Airtable Alternative",
|
||||
"html_url": "https://github.com/nocodb/nocodb",
|
||||
"language": "TypeScript",
|
||||
"stars_count": 62026,
|
||||
"forks_count": 4632,
|
||||
"stars_count": 62030,
|
||||
"forks_count": 4633,
|
||||
"open_issues_count": 592,
|
||||
"updated_at": "2026-02-17T20:03:56Z",
|
||||
"updated_at": "2026-02-18T02:31:38Z",
|
||||
"created_at": "2017-10-29T18:51:48Z",
|
||||
"clone_url": "https://github.com/nocodb/nocodb.git",
|
||||
"ssh_url": "git@github.com:nocodb/nocodb.git",
|
||||
"default_branch": "develop",
|
||||
"last_build_update": "2026-02-17T18:50:24Z"
|
||||
"last_build_update": "2026-02-18T02:34:42Z"
|
||||
}
|
||||
@@ -4,13 +4,13 @@
|
||||
"description": "Get up and running with Kimi-K2.5, GLM-5, MiniMax, DeepSeek, gpt-oss, Qwen, Gemma and other models.",
|
||||
"html_url": "https://github.com/ollama/ollama",
|
||||
"language": "Go",
|
||||
"stars_count": 162779,
|
||||
"stars_count": 162798,
|
||||
"forks_count": 14601,
|
||||
"open_issues_count": 2431,
|
||||
"updated_at": "2026-02-17T20:40:13Z",
|
||||
"open_issues_count": 2426,
|
||||
"updated_at": "2026-02-18T03:55:46Z",
|
||||
"created_at": "2023-06-26T19:39:32Z",
|
||||
"clone_url": "https://github.com/ollama/ollama.git",
|
||||
"ssh_url": "git@github.com:ollama/ollama.git",
|
||||
"default_branch": "main",
|
||||
"last_build_update": "2026-02-17T21:16:23Z"
|
||||
"last_build_update": "2026-02-18T02:18:06Z"
|
||||
}
|
||||
@@ -4,10 +4,10 @@
|
||||
"description": "Documentation that simply works",
|
||||
"html_url": "https://github.com/squidfunk/mkdocs-material",
|
||||
"language": "Python",
|
||||
"stars_count": 26063,
|
||||
"stars_count": 26068,
|
||||
"forks_count": 4044,
|
||||
"open_issues_count": 2,
|
||||
"updated_at": "2026-02-17T20:20:41Z",
|
||||
"updated_at": "2026-02-18T01:56:58Z",
|
||||
"created_at": "2016-01-28T22:09:23Z",
|
||||
"clone_url": "https://github.com/squidfunk/mkdocs-material.git",
|
||||
"ssh_url": "git@github.com:squidfunk/mkdocs-material.git",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en" class="no-js">
|
||||
<head>
|
||||
@@ -15,7 +16,7 @@
|
||||
<link rel="canonical" href="https://bnkserve.org/blog/">
|
||||
|
||||
|
||||
<link rel="prev" href="..">
|
||||
<link rel="prev" href="../docs/troubleshooting/">
|
||||
|
||||
|
||||
|
||||
@@ -104,6 +105,142 @@
|
||||
</div>
|
||||
<div data-md-component="announce">
|
||||
|
||||
<aside class="md-banner">
|
||||
<div class="md-banner__inner md-grid md-typeset">
|
||||
|
||||
<button class="md-banner__button md-icon" aria-label="Don't show this again">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
|
||||
</button>
|
||||
|
||||
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<nav class="cm-header-nav" role="navigation" aria-label="Application">
|
||||
<div class="cm-header-nav__inner">
|
||||
<a href="#" data-path="/campaigns" class="cm-header-nav__link">
|
||||
<span class="material-icons">campaign</span>
|
||||
<span class="cm-header-nav__label">Campaigns</span>
|
||||
</a>
|
||||
<a href="#" data-path="/map" class="cm-header-nav__link">
|
||||
<span class="material-icons">map</span>
|
||||
<span class="cm-header-nav__label">Map</span>
|
||||
</a>
|
||||
<a href="#" data-path="/shifts" class="cm-header-nav__link">
|
||||
<span class="material-icons">groups</span>
|
||||
<span class="cm-header-nav__label">Volunteer</span>
|
||||
</a>
|
||||
<a href="#" data-path="/gallery" class="cm-header-nav__link">
|
||||
<span class="material-icons">play_circle</span>
|
||||
<span class="cm-header-nav__label">Gallery</span>
|
||||
</a>
|
||||
<a href="#" data-path="/responses" class="cm-header-nav__link">
|
||||
<span class="material-icons">forum</span>
|
||||
<span class="cm-header-nav__label">Responses</span>
|
||||
</a>
|
||||
<a href="#" data-path="/donate" class="cm-header-nav__link">
|
||||
<span class="material-icons">favorite</span>
|
||||
<span class="cm-header-nav__label">Donate</span>
|
||||
</a>
|
||||
<a href="#" data-path="/login" class="cm-header-nav__link">
|
||||
<span class="material-icons">login</span>
|
||||
<span class="cm-header-nav__label">Sign In</span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<script>
|
||||
// Resolve nav link hrefs based on the current browser hostname.
|
||||
// localhost → http://localhost:{ADMIN_PORT}
|
||||
// subdomain.example.org → {proto}://app.example.org
|
||||
(function() {
|
||||
var h = location.hostname;
|
||||
var base;
|
||||
if (h === 'localhost' || h === '127.0.0.1') {
|
||||
base = location.protocol + '//localhost:' + (3002 || 3000);
|
||||
} else {
|
||||
var parts = h.split('.');
|
||||
if (parts.length >= 3) { parts[0] = 'app'; }
|
||||
else { parts.unshift('app'); }
|
||||
base = location.protocol + '//' + parts.join('.');
|
||||
}
|
||||
var links = document.querySelectorAll('.cm-header-nav__link[data-path]');
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
links[i].setAttribute('href', base + links[i].getAttribute('data-path'));
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<style>
|
||||
/* Override MkDocs Material announce bar container */
|
||||
.md-banner {
|
||||
background: #6f42c1 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
/* Hide the dismiss (X) button that Material adds for announce.dismiss */
|
||||
.md-banner__button {
|
||||
display: none !important;
|
||||
}
|
||||
.cm-header-nav {
|
||||
background: #6f42c1;
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 24px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
z-index: 10;
|
||||
}
|
||||
.cm-header-nav__inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
max-width: 1400px;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
flex-wrap: nowrap;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.cm-header-nav__link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 16px;
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
border-radius: 6px;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
transition: background 0.15s, transform 0.1s;
|
||||
white-space: nowrap;
|
||||
line-height: 1;
|
||||
}
|
||||
.cm-header-nav__link:hover {
|
||||
background: rgba(255,255,255,0.15);
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.cm-header-nav__link:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.cm-header-nav__link .material-icons {
|
||||
font-size: 16px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.cm-header-nav { padding: 0 8px; }
|
||||
.cm-header-nav__label { display: none; }
|
||||
.cm-header-nav__link { padding: 8px 10px; }
|
||||
.cm-header-nav__inner { gap: 4px; }
|
||||
}
|
||||
</style>
|
||||
|
||||
</div>
|
||||
|
||||
<script>var el=document.querySelector("[data-md-component=announce]");if(el){var content=el.querySelector(".md-typeset");__md_hash(content.innerHTML)===__md_get("__announce")&&(el.hidden=!0)}</script>
|
||||
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -261,6 +398,30 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-tabs__item">
|
||||
<a href="../docs/" class="md-tabs__link">
|
||||
|
||||
|
||||
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 21.5c-1.35-.85-3.8-1.5-5.5-1.5-1.65 0-3.35.3-4.75 1.05-.1.05-.15.05-.25.05-.25 0-.5-.25-.5-.5V6c.6-.45 1.25-.75 2-1 1.11-.35 2.33-.5 3.5-.5 1.95 0 4.05.4 5.5 1.5 1.45-1.1 3.55-1.5 5.5-1.5 1.17 0 2.39.15 3.5.5.75.25 1.4.55 2 1v14.6c0 .25-.25.5-.5.5-.1 0-.15 0-.25-.05-1.4-.75-3.1-1.05-4.75-1.05-1.7 0-4.15.65-5.5 1.5M12 8v11.5c1.35-.85 3.8-1.5 5.5-1.5 1.2 0 2.4.15 3.5.5V7c-1.1-.35-2.3-.5-3.5-.5-1.7 0-4.15.65-5.5 1.5m1 3.5c1.11-.68 2.6-1 4.5-1 .91 0 1.76.09 2.5.28V9.23c-.87-.15-1.71-.23-2.5-.23q-2.655 0-4.5.84zm4.5.17c-1.71 0-3.21.26-4.5.79v1.69c1.11-.65 2.6-.99 4.5-.99 1.04 0 1.88.08 2.5.24v-1.5c-.87-.16-1.71-.23-2.5-.23m2.5 2.9c-.87-.16-1.71-.24-2.5-.24-1.83 0-3.33.27-4.5.8v1.69c1.11-.66 2.6-.99 4.5-.99 1.04 0 1.88.08 2.5.24z"/></svg>
|
||||
|
||||
|
||||
Docs
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -362,6 +523,81 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="../docs/" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
Docs
|
||||
|
||||
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
|
||||
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -382,8 +618,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -393,7 +627,7 @@
|
||||
|
||||
|
||||
|
||||
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2" checked>
|
||||
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_3" checked>
|
||||
|
||||
|
||||
<div class="md-nav__link md-nav__container">
|
||||
@@ -415,14 +649,14 @@
|
||||
</a>
|
||||
|
||||
|
||||
<label class="md-nav__link md-nav__link--active" for="__nav_2" id="__nav_2_label" tabindex="">
|
||||
<label class="md-nav__link md-nav__link--active" for="__nav_3" id="__nav_3_label" tabindex="">
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
|
||||
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_2_label" aria-expanded="true">
|
||||
<label class="md-nav__title" for="__nav_2">
|
||||
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_3_label" aria-expanded="true">
|
||||
<label class="md-nav__title" for="__nav_3">
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
|
||||
|
||||
@@ -527,7 +761,7 @@
|
||||
<nav class="md-footer__inner md-grid" aria-label="Footer" >
|
||||
|
||||
|
||||
<a href=".." class="md-footer__link md-footer__link--prev" aria-label="Previous: Home">
|
||||
<a href="../docs/troubleshooting/" class="md-footer__link md-footer__link--prev" aria-label="Previous: Troubleshooting">
|
||||
<div class="md-footer__button md-icon">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
|
||||
@@ -537,7 +771,7 @@
|
||||
Previous
|
||||
</span>
|
||||
<div class="md-ellipsis">
|
||||
Home
|
||||
Troubleshooting
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
@@ -592,7 +826,7 @@
|
||||
|
||||
|
||||
|
||||
<script id="__config" type="application/json">{"annotate": null, "base": "..", "features": ["announce.dismiss", "content.action.edit", "content.action.view", "content.code.annotate", "content.code.copy", "content.tooltips", "navigation.expand", "navigation.footer", "navigation.indexes", "navigation.path", "navigation.prune", "navigation.sections", "navigation.tabs", "navigation.tabs.sticky", "navigation.top", "navigation.tracking", "search.highlight", "search.share", "search.suggest", "toc.follow"], "search": "../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
|
||||
<script id="__config" type="application/json">{"annotate": null, "base": "..", "features": ["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"], "search": "../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
|
||||
|
||||
|
||||
<script src="../assets/javascripts/bundle.79ae519e.min.js"></script>
|
||||
|
||||
1512
mkdocs/site/docs/admin/index.html
Normal file
5862
mkdocs/site/docs/api/index.html
Normal file
1579
mkdocs/site/docs/architecture/index.html
Normal file
2650
mkdocs/site/docs/deployment/index.html
Normal file
1934
mkdocs/site/docs/features/index.html
Normal file
3444
mkdocs/site/docs/getting-started/environment-variables/index.html
Normal file
1776
mkdocs/site/docs/getting-started/index.html
Normal file
1646
mkdocs/site/docs/index.html
Normal file
1967
mkdocs/site/docs/services/index.html
Normal file
1573
mkdocs/site/docs/troubleshooting/index.html
Normal file
1715
mkdocs/site/docs/volunteer/index.html
Normal file
74
mkdocs/site/header-config.json
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"enabled": true,
|
||||
"items": [
|
||||
{
|
||||
"id": "campaigns",
|
||||
"label": "Campaigns",
|
||||
"path": "/campaigns",
|
||||
"icon": "campaign",
|
||||
"enabled": true,
|
||||
"order": 0,
|
||||
"type": "builtin"
|
||||
},
|
||||
{
|
||||
"id": "map",
|
||||
"label": "Map",
|
||||
"path": "/map",
|
||||
"icon": "map",
|
||||
"enabled": true,
|
||||
"order": 1,
|
||||
"type": "builtin"
|
||||
},
|
||||
{
|
||||
"id": "shifts",
|
||||
"label": "Volunteer",
|
||||
"path": "/shifts",
|
||||
"icon": "groups",
|
||||
"enabled": true,
|
||||
"order": 2,
|
||||
"type": "builtin"
|
||||
},
|
||||
{
|
||||
"id": "gallery",
|
||||
"label": "Gallery",
|
||||
"path": "/gallery",
|
||||
"icon": "play_circle",
|
||||
"enabled": true,
|
||||
"order": 3,
|
||||
"type": "builtin"
|
||||
},
|
||||
{
|
||||
"id": "responses",
|
||||
"label": "Responses",
|
||||
"path": "/responses",
|
||||
"icon": "forum",
|
||||
"enabled": true,
|
||||
"order": 4,
|
||||
"type": "builtin"
|
||||
},
|
||||
{
|
||||
"id": "donate",
|
||||
"label": "Donate",
|
||||
"path": "/donate",
|
||||
"icon": "favorite",
|
||||
"enabled": true,
|
||||
"order": 5,
|
||||
"type": "builtin"
|
||||
},
|
||||
{
|
||||
"id": "login",
|
||||
"label": "Sign In",
|
||||
"path": "/login",
|
||||
"icon": "login",
|
||||
"enabled": true,
|
||||
"order": 6,
|
||||
"type": "builtin"
|
||||
}
|
||||
],
|
||||
"style": {
|
||||
"backgroundColor": "#6f42c1",
|
||||
"textColor": "#ffffff",
|
||||
"hoverColor": "rgba(255,255,255,0.15)",
|
||||
"height": "40px"
|
||||
}
|
||||
}
|
||||
@@ -18,22 +18,36 @@ def on_config(config: Dict[str, Any]) -> Dict[str, Any]:
|
||||
Hook that runs when MkDocs loads the configuration.
|
||||
Injects environment variables as extra JavaScript.
|
||||
"""
|
||||
import re
|
||||
|
||||
# Read environment variables (with fallbacks)
|
||||
media_api_url = os.environ.get('MEDIA_API_PUBLIC_URL', 'http://localhost:4100')
|
||||
public_url = os.environ.get('ADMIN_URL', 'http://localhost:3000')
|
||||
|
||||
# For production, check for public-facing URLs
|
||||
# Fallback to subdomain-based URLs if available
|
||||
media_api_port = os.environ.get('MEDIA_API_PORT', '4100')
|
||||
admin_port = os.environ.get('ADMIN_PORT', '3000')
|
||||
admin_url = os.environ.get('ADMIN_URL', '')
|
||||
base_domain = os.environ.get('BASE_DOMAIN', '')
|
||||
|
||||
if base_domain and not base_domain.startswith('http'):
|
||||
base_domain = f'https://{base_domain}'
|
||||
|
||||
# Use base_domain to construct URLs if env vars not explicitly set
|
||||
if media_api_url == 'http://localhost:4100' and base_domain:
|
||||
media_api_url = base_domain.replace('cmlite.org', 'media.cmlite.org')
|
||||
# Helper: detect Docker container hostnames (not browser-accessible)
|
||||
def is_docker_hostname(url: str) -> bool:
|
||||
"""Check if URL uses a Docker container hostname instead of localhost/domain."""
|
||||
host = re.sub(r'^https?://', '', url).split(':')[0].split('/')[0]
|
||||
# Docker hostnames typically contain hyphens and no dots (not localhost, not a domain)
|
||||
return host != 'localhost' and '.' not in host
|
||||
|
||||
if public_url == 'http://localhost:3000' and base_domain:
|
||||
public_url = base_domain.replace('cmlite.org', 'app.cmlite.org')
|
||||
# Resolve media API URL — must be browser-accessible (not Docker hostname)
|
||||
if is_docker_hostname(media_api_url):
|
||||
media_api_url = f'http://localhost:{media_api_port}'
|
||||
|
||||
# Resolve public URL (admin app)
|
||||
if admin_url and not is_docker_hostname(admin_url) and 'localhost' not in admin_url:
|
||||
# Production domain — use as-is
|
||||
public_url = admin_url
|
||||
else:
|
||||
# Dev: use ADMIN_PORT (most reliable source of truth)
|
||||
public_url = f'http://localhost:{admin_port}'
|
||||
|
||||
# Create inline JavaScript with config
|
||||
config_script = f"""
|
||||
@@ -85,6 +99,16 @@ def on_config(config: Dict[str, Any]) -> Dict[str, Any]:
|
||||
if env_config_path not in config['extra_javascript']:
|
||||
config['extra_javascript'].insert(0, env_config_path)
|
||||
|
||||
# Inject public_url and admin_port into config.extra so Jinja2 templates
|
||||
# can use {{ config.extra.public_url }} and {{ config.extra.admin_port }}
|
||||
# for cross-site navigation links with client-side URL resolution
|
||||
if 'extra' not in config:
|
||||
config['extra'] = {}
|
||||
config['extra']['public_url'] = public_url
|
||||
config['extra']['admin_port'] = int(admin_port)
|
||||
logger.info(f" Injected config.extra.public_url: {public_url}")
|
||||
logger.info(f" Injected config.extra.admin_port: {admin_port}")
|
||||
|
||||
return config
|
||||
|
||||
def on_pre_build(config: Dict[str, Any]) -> None:
|
||||
|
||||
@@ -1620,7 +1620,7 @@
|
||||
<li><a href="#features">Features</a></li>
|
||||
<li><a href="#live-sites">Live Sites</a></li>
|
||||
<li><a href="#pricing">Pricing</a></li>
|
||||
<li><a href="/v2/">Docs</a></li>
|
||||
<li><a href="/docs/">Docs</a></li>
|
||||
</ul>
|
||||
<button class="theme-toggle" id="theme-toggle" aria-label="Toggle dark/light theme" type="button">
|
||||
<svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true">
|
||||
@@ -1648,7 +1648,7 @@
|
||||
<a href="#features">Features</a>
|
||||
<a href="#live-sites">Live Sites</a>
|
||||
<a href="#pricing">Pricing</a>
|
||||
<a href="/v2/">Documentation</a>
|
||||
<a href="/docs/">Documentation</a>
|
||||
<a href="https://gitea.bnkops.com/admin/changemaker.lite" target="_blank" rel="noopener">Source Code</a>
|
||||
<a href="#get-started" class="btn-primary" style="text-align:center; margin-top:1rem;">Get Started</a>
|
||||
</div>
|
||||
@@ -1679,7 +1679,7 @@
|
||||
</p>
|
||||
<div class="hero-cta">
|
||||
<a href="mailto:cmlite@bnkops.ca?subject=Request%20to%20Chat%20-%20CMLITE&body=Hi%20CMlite%20Team%2C%20I%20would%20like%20to%20chat!%20Please%20send%20me%20a%20email%20back.%20Cheers%2C%20" class="btn-primary">Schedule a Chat <span aria-hidden="true">→</span></a>
|
||||
<a href="/v2/" class="btn-secondary">Read the Docs</a>
|
||||
<a href="/docs/" class="btn-secondary">Read the Docs</a>
|
||||
<a href="https://gitea.bnkops.com/admin/changemaker.lite" class="btn-secondary" target="_blank" rel="noopener">Source Code</a>
|
||||
</div>
|
||||
|
||||
@@ -2169,7 +2169,7 @@
|
||||
<li>Your infrastructure</li>
|
||||
<li>100% open source</li>
|
||||
</ul>
|
||||
<a href="/v2/getting-started/quick-start/" class="btn-secondary" style="width:100%;justify-content:center;">Installation Guide</a>
|
||||
<a href="/docs/getting-started/" class="btn-secondary" style="width:100%;justify-content:center;">Installation Guide</a>
|
||||
<p class="pricing-note">For tech-savvy campaigns</p>
|
||||
</div>
|
||||
<div class="pricing-card featured">
|
||||
@@ -2225,7 +2225,7 @@
|
||||
<p>Join campaigns using open-source tools to build real political power.</p>
|
||||
<div class="cta-buttons">
|
||||
<a href="mailto:cmlite@bnkops.ca?subject=Request%20to%20Chat%20-%20CMLITE&body=Hi%20CMlite%20Team%2C%20I%20would%20like%20to%20chat!%20Please%20send%20me%20a%20email%20back.%20Cheers%2C%20" class="btn-primary">Schedule a Chat <span aria-hidden="true">→</span></a>
|
||||
<a href="/v2/" class="btn-secondary">Read Documentation</a>
|
||||
<a href="/docs/" class="btn-secondary">Read Documentation</a>
|
||||
</div>
|
||||
<div class="cta-meta">
|
||||
<span>30-minute setup</span>
|
||||
@@ -2253,9 +2253,9 @@
|
||||
<div class="footer-section">
|
||||
<h4>Documentation</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/v2/">V2 Docs</a></li>
|
||||
<li><a href="/v2/getting-started/quick-start/">Quick Start</a></li>
|
||||
<li><a href="/v2/architecture/">Architecture</a></li>
|
||||
<li><a href="/docs/">Docs</a></li>
|
||||
<li><a href="/docs/getting-started/">Getting Started</a></li>
|
||||
<li><a href="/docs/architecture/">Architecture</a></li>
|
||||
<li><a href="/blog/">Blog</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -1620,7 +1620,7 @@
|
||||
<li><a href="#features">Features</a></li>
|
||||
<li><a href="#live-sites">Live Sites</a></li>
|
||||
<li><a href="#pricing">Pricing</a></li>
|
||||
<li><a href="/v2/">Docs</a></li>
|
||||
<li><a href="/docs/">Docs</a></li>
|
||||
</ul>
|
||||
<button class="theme-toggle" id="theme-toggle" aria-label="Toggle dark/light theme" type="button">
|
||||
<svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true">
|
||||
@@ -1648,7 +1648,7 @@
|
||||
<a href="#features">Features</a>
|
||||
<a href="#live-sites">Live Sites</a>
|
||||
<a href="#pricing">Pricing</a>
|
||||
<a href="/v2/">Documentation</a>
|
||||
<a href="/docs/">Documentation</a>
|
||||
<a href="https://gitea.bnkops.com/admin/changemaker.lite" target="_blank" rel="noopener">Source Code</a>
|
||||
<a href="#get-started" class="btn-primary" style="text-align:center; margin-top:1rem;">Get Started</a>
|
||||
</div>
|
||||
@@ -1679,7 +1679,7 @@
|
||||
</p>
|
||||
<div class="hero-cta">
|
||||
<a href="mailto:cmlite@bnkops.ca?subject=Request%20to%20Chat%20-%20CMLITE&body=Hi%20CMlite%20Team%2C%20I%20would%20like%20to%20chat!%20Please%20send%20me%20a%20email%20back.%20Cheers%2C%20" class="btn-primary">Schedule a Chat <span aria-hidden="true">→</span></a>
|
||||
<a href="/v2/" class="btn-secondary">Read the Docs</a>
|
||||
<a href="/docs/" class="btn-secondary">Read the Docs</a>
|
||||
<a href="https://gitea.bnkops.com/admin/changemaker.lite" class="btn-secondary" target="_blank" rel="noopener">Source Code</a>
|
||||
</div>
|
||||
|
||||
@@ -2169,7 +2169,7 @@
|
||||
<li>Your infrastructure</li>
|
||||
<li>100% open source</li>
|
||||
</ul>
|
||||
<a href="/v2/getting-started/quick-start/" class="btn-secondary" style="width:100%;justify-content:center;">Installation Guide</a>
|
||||
<a href="/docs/getting-started/" class="btn-secondary" style="width:100%;justify-content:center;">Installation Guide</a>
|
||||
<p class="pricing-note">For tech-savvy campaigns</p>
|
||||
</div>
|
||||
<div class="pricing-card featured">
|
||||
@@ -2225,7 +2225,7 @@
|
||||
<p>Join campaigns using open-source tools to build real political power.</p>
|
||||
<div class="cta-buttons">
|
||||
<a href="mailto:cmlite@bnkops.ca?subject=Request%20to%20Chat%20-%20CMLITE&body=Hi%20CMlite%20Team%2C%20I%20would%20like%20to%20chat!%20Please%20send%20me%20a%20email%20back.%20Cheers%2C%20" class="btn-primary">Schedule a Chat <span aria-hidden="true">→</span></a>
|
||||
<a href="/v2/" class="btn-secondary">Read Documentation</a>
|
||||
<a href="/docs/" class="btn-secondary">Read Documentation</a>
|
||||
</div>
|
||||
<div class="cta-meta">
|
||||
<span>30-minute setup</span>
|
||||
@@ -2253,9 +2253,9 @@
|
||||
<div class="footer-section">
|
||||
<h4>Documentation</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/v2/">V2 Docs</a></li>
|
||||
<li><a href="/v2/getting-started/quick-start/">Quick Start</a></li>
|
||||
<li><a href="/v2/architecture/">Architecture</a></li>
|
||||
<li><a href="/docs/">Docs</a></li>
|
||||
<li><a href="/docs/getting-started/">Getting Started</a></li>
|
||||
<li><a href="/docs/architecture/">Architecture</a></li>
|
||||
<li><a href="/blog/">Blog</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
817
mkdocs/site/main/index.html
Normal file
@@ -0,0 +1,817 @@
|
||||
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en" class="no-js">
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
|
||||
<meta name="description" content="Build Power. Not Rent It. Own your digital infrastructure.">
|
||||
|
||||
|
||||
<meta name="author" content="Bunker Operations">
|
||||
|
||||
|
||||
<link rel="canonical" href="https://bnkserve.org/main/">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="icon" href="../assets/favicon.png">
|
||||
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.1">
|
||||
|
||||
|
||||
|
||||
<title>main - Changemaker Lite</title>
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../assets/stylesheets/main.484c7ddc.min.css">
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../assets/stylesheets/palette.ab4e12ef.min.css">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300,300i,400,400i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
|
||||
<style>:root{--md-text-font:"Inter";--md-code-font:"JetBrains Mono"}</style>
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../stylesheets/extra.css">
|
||||
|
||||
<link rel="stylesheet" href="../stylesheets/home.css">
|
||||
|
||||
<link rel="stylesheet" href="../assets/css/video-player.css">
|
||||
|
||||
<script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="main - Changemaker Lite" />
|
||||
<meta property="og:description" content="Build Power. Not Rent It. Own your digital infrastructure." />
|
||||
<meta property="og:image" content="https://bnkserve.org/assets/images/social/main.png" />
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta property="og:url" content="https://bnkserve.org/main/" />
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:title" content="main - Changemaker Lite" />
|
||||
<meta property="twitter:description" content="Build Power. Not Rent It. Own your digital infrastructure." />
|
||||
<meta property="twitter:image" content="https://bnkserve.org/assets/images/social/main.png" />
|
||||
</head>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<body dir="ltr" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="amber">
|
||||
|
||||
|
||||
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
|
||||
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
|
||||
<label class="md-overlay" for="__drawer"></label>
|
||||
<div data-md-component="skip">
|
||||
|
||||
</div>
|
||||
<div data-md-component="announce">
|
||||
|
||||
<aside class="md-banner">
|
||||
<div class="md-banner__inner md-grid md-typeset">
|
||||
|
||||
<button class="md-banner__button md-icon" aria-label="Don't show this again">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
|
||||
</button>
|
||||
|
||||
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<nav class="cm-header-nav" role="navigation" aria-label="Application">
|
||||
<div class="cm-header-nav__inner">
|
||||
<a href="#" data-path="/campaigns" class="cm-header-nav__link">
|
||||
<span class="material-icons">campaign</span>
|
||||
<span class="cm-header-nav__label">Campaigns</span>
|
||||
</a>
|
||||
<a href="#" data-path="/map" class="cm-header-nav__link">
|
||||
<span class="material-icons">map</span>
|
||||
<span class="cm-header-nav__label">Map</span>
|
||||
</a>
|
||||
<a href="#" data-path="/shifts" class="cm-header-nav__link">
|
||||
<span class="material-icons">groups</span>
|
||||
<span class="cm-header-nav__label">Volunteer</span>
|
||||
</a>
|
||||
<a href="#" data-path="/gallery" class="cm-header-nav__link">
|
||||
<span class="material-icons">play_circle</span>
|
||||
<span class="cm-header-nav__label">Gallery</span>
|
||||
</a>
|
||||
<a href="#" data-path="/responses" class="cm-header-nav__link">
|
||||
<span class="material-icons">forum</span>
|
||||
<span class="cm-header-nav__label">Responses</span>
|
||||
</a>
|
||||
<a href="#" data-path="/donate" class="cm-header-nav__link">
|
||||
<span class="material-icons">favorite</span>
|
||||
<span class="cm-header-nav__label">Donate</span>
|
||||
</a>
|
||||
<a href="#" data-path="/login" class="cm-header-nav__link">
|
||||
<span class="material-icons">login</span>
|
||||
<span class="cm-header-nav__label">Sign In</span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<script>
|
||||
// Resolve nav link hrefs based on the current browser hostname.
|
||||
// localhost → http://localhost:{ADMIN_PORT}
|
||||
// subdomain.example.org → {proto}://app.example.org
|
||||
(function() {
|
||||
var h = location.hostname;
|
||||
var base;
|
||||
if (h === 'localhost' || h === '127.0.0.1') {
|
||||
base = location.protocol + '//localhost:' + (3002 || 3000);
|
||||
} else {
|
||||
var parts = h.split('.');
|
||||
if (parts.length >= 3) { parts[0] = 'app'; }
|
||||
else { parts.unshift('app'); }
|
||||
base = location.protocol + '//' + parts.join('.');
|
||||
}
|
||||
var links = document.querySelectorAll('.cm-header-nav__link[data-path]');
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
links[i].setAttribute('href', base + links[i].getAttribute('data-path'));
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<style>
|
||||
/* Override MkDocs Material announce bar container */
|
||||
.md-banner {
|
||||
background: #6f42c1 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
/* Hide the dismiss (X) button that Material adds for announce.dismiss */
|
||||
.md-banner__button {
|
||||
display: none !important;
|
||||
}
|
||||
.cm-header-nav {
|
||||
background: #6f42c1;
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 24px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
z-index: 10;
|
||||
}
|
||||
.cm-header-nav__inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
max-width: 1400px;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
flex-wrap: nowrap;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.cm-header-nav__link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 16px;
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
border-radius: 6px;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
transition: background 0.15s, transform 0.1s;
|
||||
white-space: nowrap;
|
||||
line-height: 1;
|
||||
}
|
||||
.cm-header-nav__link:hover {
|
||||
background: rgba(255,255,255,0.15);
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.cm-header-nav__link:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.cm-header-nav__link .material-icons {
|
||||
font-size: 16px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.cm-header-nav { padding: 0 8px; }
|
||||
.cm-header-nav__label { display: none; }
|
||||
.cm-header-nav__link { padding: 8px 10px; }
|
||||
.cm-header-nav__inner { gap: 4px; }
|
||||
}
|
||||
</style>
|
||||
|
||||
</div>
|
||||
|
||||
<script>var el=document.querySelector("[data-md-component=announce]");if(el){var content=el.querySelector(".md-typeset");__md_hash(content.innerHTML)===__md_get("__announce")&&(el.hidden=!0)}</script>
|
||||
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<header class="md-header md-header--shadow md-header--lifted" data-md-component="header">
|
||||
<nav class="md-header__inner md-grid" aria-label="Header">
|
||||
<a href=".." title="Changemaker Lite" class="md-header__button md-logo" aria-label="Changemaker Lite" data-md-component="logo">
|
||||
|
||||
<img src="../assets/logo.png" alt="logo">
|
||||
|
||||
</a>
|
||||
<label class="md-header__button md-icon" for="__drawer">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
|
||||
</label>
|
||||
<div class="md-header__title" data-md-component="header-title">
|
||||
<div class="md-header__ellipsis">
|
||||
<div class="md-header__topic">
|
||||
<span class="md-ellipsis">
|
||||
Changemaker Lite
|
||||
</span>
|
||||
</div>
|
||||
<div class="md-header__topic" data-md-component="header-topic">
|
||||
<span class="md-ellipsis">
|
||||
|
||||
main
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<form class="md-header__option" data-md-component="palette">
|
||||
|
||||
|
||||
|
||||
|
||||
<input class="md-option" data-md-color-media="" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="amber" aria-label="Switch to light mode" type="radio" name="__palette" id="__palette_0">
|
||||
|
||||
<label class="md-header__button md-icon" title="Switch to light mode" for="__palette_1" hidden>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m17.75 4.09-2.53 1.94.91 3.06-2.63-1.81-2.63 1.81.91-3.06-2.53-1.94L12.44 4l1.06-3 1.06 3zm3.5 6.91-1.64 1.25.59 1.98-1.7-1.17-1.7 1.17.59-1.98L15.75 11l2.06-.05L18.5 9l.69 1.95zm-2.28 4.95c.83-.08 1.72 1.1 1.19 1.85-.32.45-.66.87-1.08 1.27C15.17 23 8.84 23 4.94 19.07c-3.91-3.9-3.91-10.24 0-14.14.4-.4.82-.76 1.27-1.08.75-.53 1.93.36 1.85 1.19-.27 2.86.69 5.83 2.89 8.02a9.96 9.96 0 0 0 8.02 2.89m-1.64 2.02a12.08 12.08 0 0 1-7.8-3.47c-2.17-2.19-3.33-5-3.49-7.82-2.81 3.14-2.7 7.96.31 10.98 3.02 3.01 7.84 3.12 10.98.31"/></svg>
|
||||
</label>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<input class="md-option" data-md-color-media="" data-md-color-scheme="default" data-md-color-primary="deep-purple" data-md-color-accent="amber" aria-label="Switch to dark mode" type="radio" name="__palette" id="__palette_1">
|
||||
|
||||
<label class="md-header__button md-icon" title="Switch to dark mode" for="__palette_0" hidden>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 7a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5m0 2a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3m0-7 2.39 3.42C13.65 5.15 12.84 5 12 5s-1.65.15-2.39.42zM3.34 7l4.16-.35A7.2 7.2 0 0 0 5.94 8.5c-.44.74-.69 1.5-.83 2.29zm.02 10 1.76-3.77a7.131 7.131 0 0 0 2.38 4.14zM20.65 7l-1.77 3.79a7.02 7.02 0 0 0-2.38-4.15zm-.01 10-4.14.36c.59-.51 1.12-1.14 1.54-1.86.42-.73.69-1.5.83-2.29zM12 22l-2.41-3.44c.74.27 1.55.44 2.41.44.82 0 1.63-.17 2.37-.44z"/></svg>
|
||||
</label>
|
||||
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<label class="md-header__button md-icon" for="__search">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
|
||||
</label>
|
||||
<div class="md-search" data-md-component="search" role="dialog">
|
||||
<label class="md-search__overlay" for="__search"></label>
|
||||
<div class="md-search__inner" role="search">
|
||||
<form class="md-search__form" name="search">
|
||||
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
|
||||
<label class="md-search__icon md-icon" for="__search">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
|
||||
</label>
|
||||
<nav class="md-search__options" aria-label="Search">
|
||||
|
||||
<a href="javascript:void(0)" class="md-search__icon md-icon" title="Share" aria-label="Share" data-clipboard data-clipboard-text="" data-md-component="search-share" tabindex="-1">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66 0 1.61 1.31 2.91 2.92 2.91s2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08"/></svg>
|
||||
</a>
|
||||
|
||||
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="md-search__suggest" data-md-component="search-suggest"></div>
|
||||
|
||||
</form>
|
||||
<div class="md-search__output">
|
||||
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
|
||||
<div class="md-search-result" data-md-component="search-result">
|
||||
<div class="md-search-result__meta">
|
||||
Initializing search
|
||||
</div>
|
||||
<ol class="md-search-result__list" role="presentation"></ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="md-header__source">
|
||||
<a href="https://gitea.bnkops.com/admin/changemaker.lite" title="Go to repository" class="md-source" data-md-component="source">
|
||||
<div class="md-source__icon md-icon">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
|
||||
</div>
|
||||
<div class="md-source__repository">
|
||||
changemaker.lite
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
|
||||
|
||||
|
||||
<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
|
||||
<div class="md-grid">
|
||||
<ul class="md-tabs__list">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-tabs__item">
|
||||
<a href=".." class="md-tabs__link">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Home
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-tabs__item">
|
||||
<a href="../docs/" class="md-tabs__link">
|
||||
|
||||
|
||||
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 21.5c-1.35-.85-3.8-1.5-5.5-1.5-1.65 0-3.35.3-4.75 1.05-.1.05-.15.05-.25.05-.25 0-.5-.25-.5-.5V6c.6-.45 1.25-.75 2-1 1.11-.35 2.33-.5 3.5-.5 1.95 0 4.05.4 5.5 1.5 1.45-1.1 3.55-1.5 5.5-1.5 1.17 0 2.39.15 3.5.5.75.25 1.4.55 2 1v14.6c0 .25-.25.5-.5.5-.1 0-.15 0-.25-.05-1.4-.75-3.1-1.05-4.75-1.05-1.7 0-4.15.65-5.5 1.5M12 8v11.5c1.35-.85 3.8-1.5 5.5-1.5 1.2 0 2.4.15 3.5.5V7c-1.1-.35-2.3-.5-3.5-.5-1.7 0-4.15.65-5.5 1.5m1 3.5c1.11-.68 2.6-1 4.5-1 .91 0 1.76.09 2.5.28V9.23c-.87-.15-1.71-.23-2.5-.23q-2.655 0-4.5.84zm4.5.17c-1.71 0-3.21.26-4.5.79v1.69c1.11-.65 2.6-.99 4.5-.99 1.04 0 1.88.08 2.5.24v-1.5c-.87-.16-1.71-.23-2.5-.23m2.5 2.9c-.87-.16-1.71-.24-2.5-.24-1.83 0-3.33.27-4.5.8v1.69c1.11-.66 2.6-.99 4.5-.99 1.04 0 1.88.08 2.5.24z"/></svg>
|
||||
|
||||
|
||||
Docs
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-tabs__item">
|
||||
<a href="../blog/" class="md-tabs__link">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Blog
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
</header>
|
||||
|
||||
<div class="md-container" data-md-component="container">
|
||||
|
||||
|
||||
|
||||
|
||||
<main class="md-main" data-md-component="main">
|
||||
<div class="md-main__inner md-grid">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" hidden>
|
||||
<div class="md-sidebar__scrollwrap">
|
||||
<div class="md-sidebar__inner">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<nav class="md-nav md-nav--primary md-nav--lifted" aria-label="Navigation" data-md-level="0">
|
||||
<label class="md-nav__title" for="__drawer">
|
||||
<a href=".." title="Changemaker Lite" class="md-nav__button md-logo" aria-label="Changemaker Lite" data-md-component="logo">
|
||||
|
||||
<img src="../assets/logo.png" alt="logo">
|
||||
|
||||
</a>
|
||||
Changemaker Lite
|
||||
</label>
|
||||
|
||||
<div class="md-nav__source">
|
||||
<a href="https://gitea.bnkops.com/admin/changemaker.lite" title="Go to repository" class="md-source" data-md-component="source">
|
||||
<div class="md-source__icon md-icon">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
|
||||
</div>
|
||||
<div class="md-source__repository">
|
||||
changemaker.lite
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<ul class="md-nav__list" data-md-scrollfix>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href=".." class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
Home
|
||||
|
||||
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="../docs/" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
Docs
|
||||
|
||||
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
|
||||
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="../blog/" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
Blog
|
||||
|
||||
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
|
||||
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" hidden>
|
||||
<div class="md-sidebar__scrollwrap">
|
||||
<div class="md-sidebar__inner">
|
||||
|
||||
|
||||
|
||||
|
||||
<nav class="md-nav md-nav--secondary" aria-label="On this page">
|
||||
|
||||
|
||||
|
||||
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="md-content" data-md-component="content">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<article class="md-content__inner md-typeset">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="https://gitea.bnkops.com/admin/changemaker.lite/src/branch/main/mkdocs/docs/main.md" title="Edit this page" class="md-content__button md-icon" rel="edit">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10 20H6V4h7v5h5v3.1l2-2V8l-6-6H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h4zm10.2-7c.1 0 .3.1.4.2l1.3 1.3c.2.2.2.6 0 .8l-1 1-2.1-2.1 1-1c.1-.1.2-.2.4-.2m0 3.9L14.1 23H12v-2.1l6.1-6.1z"/></svg>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="https://gitea.bnkops.com/admin/changemaker.lite/src/branch/main/mkdocs/docs/main.md" title="View source of this page" class="md-content__button md-icon">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17 18c.56 0 1 .44 1 1s-.44 1-1 1-1-.44-1-1 .44-1 1-1m0-3c-2.73 0-5.06 1.66-6 4 .94 2.34 3.27 4 6 4s5.06-1.66 6-4c-.94-2.34-3.27-4-6-4m0 6.5a2.5 2.5 0 0 1-2.5-2.5 2.5 2.5 0 0 1 2.5-2.5 2.5 2.5 0 0 1 2.5 2.5 2.5 2.5 0 0 1-2.5 2.5M9.27 20H6V4h7v5h5v4.07c.7.08 1.36.25 2 .49V8l-6-6H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h4.5a8.2 8.2 0 0 1-1.23-2"/></svg>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<h1>main</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</article>
|
||||
</div>
|
||||
|
||||
|
||||
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
|
||||
</div>
|
||||
|
||||
<button type="button" class="md-top md-icon" data-md-component="top" hidden>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg>
|
||||
Back to top
|
||||
</button>
|
||||
|
||||
</main>
|
||||
|
||||
<footer class="md-footer">
|
||||
|
||||
|
||||
|
||||
<div class="md-footer-meta md-typeset">
|
||||
<div class="md-footer-meta__inner md-grid">
|
||||
<div class="md-copyright">
|
||||
|
||||
<div class="md-copyright__highlight">
|
||||
Copyright © 2024 The Bunker Operations – <a href="#__consent">Change cookie settings</a>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="md-social">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="https://gitea.bnkops.com/admin" target="_blank" rel="noopener" title="Gitea Repository" class="md-social__link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M173.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6m-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3m44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9M252.8 8C114.1 8 8 113.3 8 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C436.2 457.8 504 362.9 504 252 504 113.3 391.5 8 252.8 8M105.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1m-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7m32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1m-11.4-14.7c-1.6 1-1.6 3.6 0 5.9s4.3 3.3 5.6 2.3c1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2"/></svg>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="https://listmonk.bnkops.com/subscription/form" target="_blank" rel="noopener" title="Newsletter" class="md-social__link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M536.4-26.3c9.8-3.5 20.6-1 28 6.3s9.8 18.2 6.3 28l-178 496.9c-5 13.9-18.1 23.1-32.8 23.1-14.2 0-27-8.6-32.3-21.7l-64.2-158c-4.5-11-2.5-23.6 5.2-32.6l94.5-112.4c5.1-6.1 4.7-15-.9-20.6s-14.6-6-20.6-.9l-112.4 94.3c-9.1 7.6-21.6 9.6-32.6 5.2L38.1 216.8c-13.1-5.3-21.7-18.1-21.7-32.3 0-14.7 9.2-27.8 23.1-32.8z"/></svg>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
<div class="md-dialog" data-md-component="dialog">
|
||||
<div class="md-dialog__inner md-typeset"></div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script id="__config" type="application/json">{"annotate": null, "base": "..", "features": ["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"], "search": "../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
|
||||
|
||||
|
||||
<script src="../assets/javascripts/bundle.79ae519e.min.js"></script>
|
||||
|
||||
<script src="../javascripts/home.js"></script>
|
||||
|
||||
<script src="../javascripts/github-widget.js"></script>
|
||||
|
||||
<script src="../javascripts/gitea-widget.js"></script>
|
||||
|
||||
<script src="../assets/js/env-config.js"></script>
|
||||
|
||||
<script src="../assets/js/video-player.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
74
mkdocs/site/overrides/header-config.json
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"enabled": true,
|
||||
"items": [
|
||||
{
|
||||
"id": "campaigns",
|
||||
"label": "Campaigns",
|
||||
"path": "/campaigns",
|
||||
"icon": "campaign",
|
||||
"enabled": true,
|
||||
"order": 0,
|
||||
"type": "builtin"
|
||||
},
|
||||
{
|
||||
"id": "map",
|
||||
"label": "Map",
|
||||
"path": "/map",
|
||||
"icon": "map",
|
||||
"enabled": true,
|
||||
"order": 1,
|
||||
"type": "builtin"
|
||||
},
|
||||
{
|
||||
"id": "shifts",
|
||||
"label": "Volunteer",
|
||||
"path": "/shifts",
|
||||
"icon": "groups",
|
||||
"enabled": true,
|
||||
"order": 2,
|
||||
"type": "builtin"
|
||||
},
|
||||
{
|
||||
"id": "gallery",
|
||||
"label": "Gallery",
|
||||
"path": "/gallery",
|
||||
"icon": "play_circle",
|
||||
"enabled": true,
|
||||
"order": 3,
|
||||
"type": "builtin"
|
||||
},
|
||||
{
|
||||
"id": "responses",
|
||||
"label": "Responses",
|
||||
"path": "/responses",
|
||||
"icon": "forum",
|
||||
"enabled": true,
|
||||
"order": 4,
|
||||
"type": "builtin"
|
||||
},
|
||||
{
|
||||
"id": "donate",
|
||||
"label": "Donate",
|
||||
"path": "/donate",
|
||||
"icon": "favorite",
|
||||
"enabled": true,
|
||||
"order": 5,
|
||||
"type": "builtin"
|
||||
},
|
||||
{
|
||||
"id": "login",
|
||||
"label": "Sign In",
|
||||
"path": "/login",
|
||||
"icon": "login",
|
||||
"enabled": true,
|
||||
"order": 6,
|
||||
"type": "builtin"
|
||||
}
|
||||
],
|
||||
"style": {
|
||||
"backgroundColor": "#6f42c1",
|
||||
"textColor": "#ffffff",
|
||||
"hoverColor": "rgba(255,255,255,0.15)",
|
||||
"height": "40px"
|
||||
}
|
||||
}
|
||||
@@ -1607,7 +1607,7 @@
|
||||
<li><a href="#features">Features</a></li>
|
||||
<li><a href="#live-sites">Live Sites</a></li>
|
||||
<li><a href="#pricing">Pricing</a></li>
|
||||
<li><a href="/v2/">Docs</a></li>
|
||||
<li><a href="/docs/">Docs</a></li>
|
||||
</ul>
|
||||
<button class="theme-toggle" id="theme-toggle" aria-label="Toggle dark/light theme" type="button">
|
||||
<svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true">
|
||||
@@ -1635,7 +1635,7 @@
|
||||
<a href="#features">Features</a>
|
||||
<a href="#live-sites">Live Sites</a>
|
||||
<a href="#pricing">Pricing</a>
|
||||
<a href="/v2/">Documentation</a>
|
||||
<a href="/docs/">Documentation</a>
|
||||
<a href="https://gitea.bnkops.com/admin/changemaker.lite" target="_blank" rel="noopener">Source Code</a>
|
||||
<a href="#get-started" class="btn-primary" style="text-align:center; margin-top:1rem;">Get Started</a>
|
||||
</div>
|
||||
@@ -1666,7 +1666,7 @@
|
||||
</p>
|
||||
<div class="hero-cta">
|
||||
<a href="mailto:cmlite@bnkops.ca?subject=Request%20to%20Chat%20-%20CMLITE&body=Hi%20CMlite%20Team%2C%20I%20would%20like%20to%20chat!%20Please%20send%20me%20a%20email%20back.%20Cheers%2C%20" class="btn-primary">Schedule a Chat <span aria-hidden="true">→</span></a>
|
||||
<a href="/v2/" class="btn-secondary">Read the Docs</a>
|
||||
<a href="/docs/" class="btn-secondary">Read the Docs</a>
|
||||
<a href="https://gitea.bnkops.com/admin/changemaker.lite" class="btn-secondary" target="_blank" rel="noopener">Source Code</a>
|
||||
</div>
|
||||
|
||||
@@ -2156,7 +2156,7 @@
|
||||
<li>Your infrastructure</li>
|
||||
<li>100% open source</li>
|
||||
</ul>
|
||||
<a href="/v2/getting-started/quick-start/" class="btn-secondary" style="width:100%;justify-content:center;">Installation Guide</a>
|
||||
<a href="/docs/getting-started/" class="btn-secondary" style="width:100%;justify-content:center;">Installation Guide</a>
|
||||
<p class="pricing-note">For tech-savvy campaigns</p>
|
||||
</div>
|
||||
<div class="pricing-card featured">
|
||||
@@ -2212,7 +2212,7 @@
|
||||
<p>Join campaigns using open-source tools to build real political power.</p>
|
||||
<div class="cta-buttons">
|
||||
<a href="mailto:cmlite@bnkops.ca?subject=Request%20to%20Chat%20-%20CMLITE&body=Hi%20CMlite%20Team%2C%20I%20would%20like%20to%20chat!%20Please%20send%20me%20a%20email%20back.%20Cheers%2C%20" class="btn-primary">Schedule a Chat <span aria-hidden="true">→</span></a>
|
||||
<a href="/v2/" class="btn-secondary">Read Documentation</a>
|
||||
<a href="/docs/" class="btn-secondary">Read Documentation</a>
|
||||
</div>
|
||||
<div class="cta-meta">
|
||||
<span>30-minute setup</span>
|
||||
@@ -2240,9 +2240,9 @@
|
||||
<div class="footer-section">
|
||||
<h4>Documentation</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="/v2/">V2 Docs</a></li>
|
||||
<li><a href="/v2/getting-started/quick-start/">Quick Start</a></li>
|
||||
<li><a href="/v2/architecture/">Architecture</a></li>
|
||||
<li><a href="/docs/">Docs</a></li>
|
||||
<li><a href="/docs/getting-started/">Getting Started</a></li>
|
||||
<li><a href="/docs/architecture/">Architecture</a></li>
|
||||
<li><a href="/blog/">Blog</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
125
mkdocs/site/overrides/main.html
Normal file
@@ -0,0 +1,125 @@
|
||||
{# Auto-generated by Changemaker Lite Header Builder — do not edit manually #}
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block announce %}
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<nav class="cm-header-nav" role="navigation" aria-label="Application">
|
||||
<div class="cm-header-nav__inner">
|
||||
<a href="#" data-path="/campaigns" class="cm-header-nav__link">
|
||||
<span class="material-icons">campaign</span>
|
||||
<span class="cm-header-nav__label">Campaigns</span>
|
||||
</a>
|
||||
<a href="#" data-path="/map" class="cm-header-nav__link">
|
||||
<span class="material-icons">map</span>
|
||||
<span class="cm-header-nav__label">Map</span>
|
||||
</a>
|
||||
<a href="#" data-path="/shifts" class="cm-header-nav__link">
|
||||
<span class="material-icons">groups</span>
|
||||
<span class="cm-header-nav__label">Volunteer</span>
|
||||
</a>
|
||||
<a href="#" data-path="/gallery" class="cm-header-nav__link">
|
||||
<span class="material-icons">play_circle</span>
|
||||
<span class="cm-header-nav__label">Gallery</span>
|
||||
</a>
|
||||
<a href="#" data-path="/responses" class="cm-header-nav__link">
|
||||
<span class="material-icons">forum</span>
|
||||
<span class="cm-header-nav__label">Responses</span>
|
||||
</a>
|
||||
<a href="#" data-path="/donate" class="cm-header-nav__link">
|
||||
<span class="material-icons">favorite</span>
|
||||
<span class="cm-header-nav__label">Donate</span>
|
||||
</a>
|
||||
<a href="#" data-path="/login" class="cm-header-nav__link">
|
||||
<span class="material-icons">login</span>
|
||||
<span class="cm-header-nav__label">Sign In</span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<script>
|
||||
// Resolve nav link hrefs based on the current browser hostname.
|
||||
// localhost → http://localhost:{ADMIN_PORT}
|
||||
// subdomain.example.org → {proto}://app.example.org
|
||||
(function() {
|
||||
var h = location.hostname;
|
||||
var base;
|
||||
if (h === 'localhost' || h === '127.0.0.1') {
|
||||
base = location.protocol + '//localhost:' + ({{ config.extra.admin_port }} || 3000);
|
||||
} else {
|
||||
var parts = h.split('.');
|
||||
if (parts.length >= 3) { parts[0] = 'app'; }
|
||||
else { parts.unshift('app'); }
|
||||
base = location.protocol + '//' + parts.join('.');
|
||||
}
|
||||
var links = document.querySelectorAll('.cm-header-nav__link[data-path]');
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
links[i].setAttribute('href', base + links[i].getAttribute('data-path'));
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<style>
|
||||
/* Override MkDocs Material announce bar container */
|
||||
.md-banner {
|
||||
background: #6f42c1 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
/* Hide the dismiss (X) button that Material adds for announce.dismiss */
|
||||
.md-banner__button {
|
||||
display: none !important;
|
||||
}
|
||||
.cm-header-nav {
|
||||
background: #6f42c1;
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 24px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
z-index: 10;
|
||||
}
|
||||
.cm-header-nav__inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
max-width: 1400px;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
flex-wrap: nowrap;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.cm-header-nav__link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 16px;
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
border-radius: 6px;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
transition: background 0.15s, transform 0.1s;
|
||||
white-space: nowrap;
|
||||
line-height: 1;
|
||||
}
|
||||
.cm-header-nav__link:hover {
|
||||
background: rgba(255,255,255,0.15);
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.cm-header-nav__link:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.cm-header-nav__link .material-icons {
|
||||
font-size: 16px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.cm-header-nav { padding: 0 8px; }
|
||||
.cm-header-nav__label { display: none; }
|
||||
.cm-header-nav__link { padding: 8px 10px; }
|
||||
.cm-header-nav__inner { gap: 4px; }
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
@@ -2,18 +2,66 @@
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://bnkserve.org/</loc>
|
||||
<lastmod>2026-02-17</lastmod>
|
||||
<lastmod>2026-02-18</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bnkserve.org/lander/</loc>
|
||||
<lastmod>2026-02-17</lastmod>
|
||||
<lastmod>2026-02-18</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bnkserve.org/main/</loc>
|
||||
<lastmod>2026-02-18</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bnkserve.org/test/</loc>
|
||||
<lastmod>2026-02-17</lastmod>
|
||||
<lastmod>2026-02-18</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bnkserve.org/blog/</loc>
|
||||
<lastmod>2026-02-17</lastmod>
|
||||
<lastmod>2026-02-18</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bnkserve.org/docs/</loc>
|
||||
<lastmod>2026-02-18</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bnkserve.org/docs/admin/</loc>
|
||||
<lastmod>2026-02-18</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bnkserve.org/docs/api/</loc>
|
||||
<lastmod>2026-02-18</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bnkserve.org/docs/architecture/</loc>
|
||||
<lastmod>2026-02-18</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bnkserve.org/docs/deployment/</loc>
|
||||
<lastmod>2026-02-18</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bnkserve.org/docs/features/</loc>
|
||||
<lastmod>2026-02-18</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bnkserve.org/docs/getting-started/</loc>
|
||||
<lastmod>2026-02-18</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bnkserve.org/docs/getting-started/environment-variables/</loc>
|
||||
<lastmod>2026-02-18</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bnkserve.org/docs/services/</loc>
|
||||
<lastmod>2026-02-18</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bnkserve.org/docs/troubleshooting/</loc>
|
||||
<lastmod>2026-02-18</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://bnkserve.org/docs/volunteer/</loc>
|
||||
<lastmod>2026-02-18</lastmod>
|
||||
</url>
|
||||
</urlset>
|
||||
@@ -1,4 +1,5 @@
|
||||
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en" class="no-js">
|
||||
<head>
|
||||
@@ -102,6 +103,142 @@
|
||||
</div>
|
||||
<div data-md-component="announce">
|
||||
|
||||
<aside class="md-banner">
|
||||
<div class="md-banner__inner md-grid md-typeset">
|
||||
|
||||
<button class="md-banner__button md-icon" aria-label="Don't show this again">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
|
||||
</button>
|
||||
|
||||
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<nav class="cm-header-nav" role="navigation" aria-label="Application">
|
||||
<div class="cm-header-nav__inner">
|
||||
<a href="#" data-path="/campaigns" class="cm-header-nav__link">
|
||||
<span class="material-icons">campaign</span>
|
||||
<span class="cm-header-nav__label">Campaigns</span>
|
||||
</a>
|
||||
<a href="#" data-path="/map" class="cm-header-nav__link">
|
||||
<span class="material-icons">map</span>
|
||||
<span class="cm-header-nav__label">Map</span>
|
||||
</a>
|
||||
<a href="#" data-path="/shifts" class="cm-header-nav__link">
|
||||
<span class="material-icons">groups</span>
|
||||
<span class="cm-header-nav__label">Volunteer</span>
|
||||
</a>
|
||||
<a href="#" data-path="/gallery" class="cm-header-nav__link">
|
||||
<span class="material-icons">play_circle</span>
|
||||
<span class="cm-header-nav__label">Gallery</span>
|
||||
</a>
|
||||
<a href="#" data-path="/responses" class="cm-header-nav__link">
|
||||
<span class="material-icons">forum</span>
|
||||
<span class="cm-header-nav__label">Responses</span>
|
||||
</a>
|
||||
<a href="#" data-path="/donate" class="cm-header-nav__link">
|
||||
<span class="material-icons">favorite</span>
|
||||
<span class="cm-header-nav__label">Donate</span>
|
||||
</a>
|
||||
<a href="#" data-path="/login" class="cm-header-nav__link">
|
||||
<span class="material-icons">login</span>
|
||||
<span class="cm-header-nav__label">Sign In</span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<script>
|
||||
// Resolve nav link hrefs based on the current browser hostname.
|
||||
// localhost → http://localhost:{ADMIN_PORT}
|
||||
// subdomain.example.org → {proto}://app.example.org
|
||||
(function() {
|
||||
var h = location.hostname;
|
||||
var base;
|
||||
if (h === 'localhost' || h === '127.0.0.1') {
|
||||
base = location.protocol + '//localhost:' + (3002 || 3000);
|
||||
} else {
|
||||
var parts = h.split('.');
|
||||
if (parts.length >= 3) { parts[0] = 'app'; }
|
||||
else { parts.unshift('app'); }
|
||||
base = location.protocol + '//' + parts.join('.');
|
||||
}
|
||||
var links = document.querySelectorAll('.cm-header-nav__link[data-path]');
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
links[i].setAttribute('href', base + links[i].getAttribute('data-path'));
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<style>
|
||||
/* Override MkDocs Material announce bar container */
|
||||
.md-banner {
|
||||
background: #6f42c1 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
/* Hide the dismiss (X) button that Material adds for announce.dismiss */
|
||||
.md-banner__button {
|
||||
display: none !important;
|
||||
}
|
||||
.cm-header-nav {
|
||||
background: #6f42c1;
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 24px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
z-index: 10;
|
||||
}
|
||||
.cm-header-nav__inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
max-width: 1400px;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
flex-wrap: nowrap;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.cm-header-nav__link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 16px;
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
border-radius: 6px;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
transition: background 0.15s, transform 0.1s;
|
||||
white-space: nowrap;
|
||||
line-height: 1;
|
||||
}
|
||||
.cm-header-nav__link:hover {
|
||||
background: rgba(255,255,255,0.15);
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.cm-header-nav__link:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.cm-header-nav__link .material-icons {
|
||||
font-size: 16px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.cm-header-nav { padding: 0 8px; }
|
||||
.cm-header-nav__label { display: none; }
|
||||
.cm-header-nav__link { padding: 8px 10px; }
|
||||
.cm-header-nav__inner { gap: 4px; }
|
||||
}
|
||||
</style>
|
||||
|
||||
</div>
|
||||
|
||||
<script>var el=document.querySelector("[data-md-component=announce]");if(el){var content=el.querySelector(".md-typeset");__md_hash(content.innerHTML)===__md_get("__announce")&&(el.hidden=!0)}</script>
|
||||
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -262,6 +399,30 @@
|
||||
|
||||
|
||||
|
||||
<li class="md-tabs__item">
|
||||
<a href="../docs/" class="md-tabs__link">
|
||||
|
||||
|
||||
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 21.5c-1.35-.85-3.8-1.5-5.5-1.5-1.65 0-3.35.3-4.75 1.05-.1.05-.15.05-.25.05-.25 0-.5-.25-.5-.5V6c.6-.45 1.25-.75 2-1 1.11-.35 2.33-.5 3.5-.5 1.95 0 4.05.4 5.5 1.5 1.45-1.1 3.55-1.5 5.5-1.5 1.17 0 2.39.15 3.5.5.75.25 1.4.55 2 1v14.6c0 .25-.25.5-.5.5-.1 0-.15 0-.25-.05-1.4-.75-3.1-1.05-4.75-1.05-1.7 0-4.15.65-5.5 1.5M12 8v11.5c1.35-.85 3.8-1.5 5.5-1.5 1.2 0 2.4.15 3.5.5V7c-1.1-.35-2.3-.5-3.5-.5-1.7 0-4.15.65-5.5 1.5m1 3.5c1.11-.68 2.6-1 4.5-1 .91 0 1.76.09 2.5.28V9.23c-.87-.15-1.71-.23-2.5-.23q-2.655 0-4.5.84zm4.5.17c-1.71 0-3.21.26-4.5.79v1.69c1.11-.65 2.6-.99 4.5-.99 1.04 0 1.88.08 2.5.24v-1.5c-.87-.16-1.71-.23-2.5-.23m2.5 2.9c-.87-.16-1.71-.24-2.5-.24-1.83 0-3.33.27-4.5.8v1.69c1.11-.66 2.6-.99 4.5-.99 1.04 0 1.88.08 2.5.24z"/></svg>
|
||||
|
||||
|
||||
Docs
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-tabs__item">
|
||||
<a href="../blog/" class="md-tabs__link">
|
||||
|
||||
@@ -369,12 +530,85 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item md-nav__item--pruned md-nav__item--nested">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="../docs/" class="md-nav__link">
|
||||
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
|
||||
|
||||
Docs
|
||||
|
||||
|
||||
|
||||
</span>
|
||||
|
||||
|
||||
|
||||
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -478,6 +712,45 @@
|
||||
|
||||
<h1 id="test">Test<a class="headerlink" href="#test" title="Permanent link">¶</a></h1>
|
||||
<p>Testing page. </p>
|
||||
<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>
|
||||
|
||||
|
||||
|
||||
@@ -556,7 +829,7 @@
|
||||
|
||||
|
||||
|
||||
<script id="__config" type="application/json">{"annotate": null, "base": "..", "features": ["announce.dismiss", "content.action.edit", "content.action.view", "content.code.annotate", "content.code.copy", "content.tooltips", "navigation.expand", "navigation.footer", "navigation.indexes", "navigation.path", "navigation.prune", "navigation.sections", "navigation.tabs", "navigation.tabs.sticky", "navigation.top", "navigation.tracking", "search.highlight", "search.share", "search.suggest", "toc.follow"], "search": "../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
|
||||
<script id="__config" type="application/json">{"annotate": null, "base": "..", "features": ["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"], "search": "../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
|
||||
|
||||
|
||||
<script src="../assets/javascripts/bundle.79ae519e.min.js"></script>
|
||||
|
||||