Tonnes of updates to the site documentation and general processes
This commit is contained in:
16
mkdocs/docs/assets/repo-data/admin-changemaker.lite.json
Normal file
16
mkdocs/docs/assets/repo-data/admin-changemaker.lite.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"full_name": "admin/changemaker.lite",
|
||||
"name": "changemaker.lite",
|
||||
"description": "Changemaker-lite is the current active development branch of Changemaker, focused on streamlining core services. These improvements will be merged into the master branch once ready.",
|
||||
"html_url": "https://gitea.bnkops.com/admin/changemaker.lite",
|
||||
"language": "HTML",
|
||||
"stars_count": 0,
|
||||
"forks_count": 0,
|
||||
"open_issues_count": 0,
|
||||
"updated_at": "2025-06-30T10:01:47-06: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": "2025-06-30T10:01:47-06:00"
|
||||
}
|
||||
16
mkdocs/docs/assets/repo-data/coder-code-server.json
Normal file
16
mkdocs/docs/assets/repo-data/coder-code-server.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"full_name": "coder/code-server",
|
||||
"name": "code-server",
|
||||
"description": "VS Code in the browser",
|
||||
"html_url": "https://github.com/coder/code-server",
|
||||
"language": "TypeScript",
|
||||
"stars_count": 72491,
|
||||
"forks_count": 6060,
|
||||
"open_issues_count": 132,
|
||||
"updated_at": "2025-06-30T21:31:29Z",
|
||||
"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",
|
||||
"default_branch": "main",
|
||||
"last_build_update": "2025-06-25T21:18:52Z"
|
||||
}
|
||||
16
mkdocs/docs/assets/repo-data/gethomepage-homepage.json
Normal file
16
mkdocs/docs/assets/repo-data/gethomepage-homepage.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"full_name": "gethomepage/homepage",
|
||||
"name": "homepage",
|
||||
"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": 24565,
|
||||
"forks_count": 1519,
|
||||
"open_issues_count": 1,
|
||||
"updated_at": "2025-06-30T20:44:09Z",
|
||||
"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": "2025-06-30T12:13:58Z"
|
||||
}
|
||||
16
mkdocs/docs/assets/repo-data/knadh-listmonk.json
Normal file
16
mkdocs/docs/assets/repo-data/knadh-listmonk.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"full_name": "knadh/listmonk",
|
||||
"name": "listmonk",
|
||||
"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": 17238,
|
||||
"forks_count": 1656,
|
||||
"open_issues_count": 103,
|
||||
"updated_at": "2025-06-30T20:57:12Z",
|
||||
"created_at": "2019-06-26T05:08:39Z",
|
||||
"clone_url": "https://github.com/knadh/listmonk.git",
|
||||
"ssh_url": "git@github.com:knadh/listmonk.git",
|
||||
"default_branch": "master",
|
||||
"last_build_update": "2025-06-30T10:45:51Z"
|
||||
}
|
||||
16
mkdocs/docs/assets/repo-data/n8n-io-n8n.json
Normal file
16
mkdocs/docs/assets/repo-data/n8n-io-n8n.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"full_name": "n8n-io/n8n",
|
||||
"name": "n8n",
|
||||
"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": 113623,
|
||||
"forks_count": 33244,
|
||||
"open_issues_count": 1061,
|
||||
"updated_at": "2025-06-30T21:38:07Z",
|
||||
"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": "2025-06-30T20:59:49Z"
|
||||
}
|
||||
16
mkdocs/docs/assets/repo-data/nocodb-nocodb.json
Normal file
16
mkdocs/docs/assets/repo-data/nocodb-nocodb.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"full_name": "nocodb/nocodb",
|
||||
"name": "nocodb",
|
||||
"description": "\ud83d\udd25 \ud83d\udd25 \ud83d\udd25 Open Source Airtable Alternative",
|
||||
"html_url": "https://github.com/nocodb/nocodb",
|
||||
"language": "TypeScript",
|
||||
"stars_count": 55465,
|
||||
"forks_count": 3980,
|
||||
"open_issues_count": 714,
|
||||
"updated_at": "2025-06-30T21:39:01Z",
|
||||
"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": "2025-06-30T20:21:14Z"
|
||||
}
|
||||
16
mkdocs/docs/assets/repo-data/squidfunk-mkdocs-material.json
Normal file
16
mkdocs/docs/assets/repo-data/squidfunk-mkdocs-material.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"full_name": "squidfunk/mkdocs-material",
|
||||
"name": "mkdocs-material",
|
||||
"description": "Documentation that simply works",
|
||||
"html_url": "https://github.com/squidfunk/mkdocs-material",
|
||||
"language": "Python",
|
||||
"stars_count": 23757,
|
||||
"forks_count": 3783,
|
||||
"open_issues_count": 4,
|
||||
"updated_at": "2025-06-30T21:37:52Z",
|
||||
"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",
|
||||
"default_branch": "master",
|
||||
"last_build_update": "2025-06-29T16:43:26Z"
|
||||
}
|
||||
147
mkdocs/docs/build/build-server.md
vendored
Normal file
147
mkdocs/docs/build/build-server.md
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
# BNKops Server Build
|
||||
|
||||
Purpose: a Ubuntu server build-out for general application
|
||||
|
||||
---
|
||||
|
||||
|
||||
This documentation is a overview of the full build out for a server OS and baseline for running Changemaker-lite. It is a manual to re-install this server on any machine.
|
||||
|
||||
All of the following systems are free and the majority are open source.
|
||||
## [Ubuntu](https://ubuntu.com/) OS
|
||||
_Ubuntu_ is a Linux distribution derived from Debian and composed mostly of free and open-source software.
|
||||
### [Install Ubuntu](https://ubuntu.com/tutorials/install-ubuntu-desktop#1-overview)
|
||||
### Post Install
|
||||
Post installation, run update:
|
||||
```
|
||||
sudo apt update
|
||||
```
|
||||
|
||||
```
|
||||
sudo apt upgrade
|
||||
```
|
||||
### Configuration
|
||||
Further configurations:
|
||||
|
||||
- User profile was updated to Automatically Login
|
||||
- Remote Desktop, Sharing, and Login have all been enabled.
|
||||
- Default system settings have been set to dark mode.
|
||||
|
||||
## [VSCode Insiders](https://code.visualstudio.com/insiders/)
|
||||
Visual Studio Code is a new choice of tool that combines the simplicity of a code editor with what developers need for the core edit-build-debug cycle.
|
||||
### Install Using App Centre
|
||||
|
||||
## [Obsidian](https://obsidian.md/)
|
||||
The free and flexible app for your private thoughts.
|
||||
### Install Using App Center
|
||||
|
||||
## [Curl](https://curl.se/)
|
||||
command line tool and library for transferring data with URLs (since 1998)
|
||||
### Install
|
||||
```
|
||||
sudo apt install curl
|
||||
```
|
||||
## [Glances](https://github.com/nicolargo/glances)
|
||||
Glances an Eye on your system. A top/htop alternative for GNU/Linux, BSD, Mac OS and Windows operating systems.
|
||||
### Install
|
||||
```
|
||||
sudo snap install glances
|
||||
```
|
||||
## [Syncthing](https://syncthing.net/)
|
||||
Syncthing is a continuous file synchronization program. It synchronizes files between two or more computers in real time, safely protected from prying eyes. Your data is your data alone and you deserve to choose where it is stored, whether it is shared with some third party, and how it’s transmitted over the internet.
|
||||
### Install
|
||||
```
|
||||
# Add the release PGP keys:
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
sudo curl -L -o /etc/apt/keyrings/syncthing-archive-keyring.gpg https://syncthing.net/release-key.gpg
|
||||
```
|
||||
|
||||
```
|
||||
# Add the "stable" channel to your APT sources:
|
||||
echo "deb [signed-by=/etc/apt/keyrings/syncthing-archive-keyring.gpg] https://apt.syncthing.net/ syncthing stable" | sudo tee /etc/apt/sources.list.d/syncthing.list
|
||||
```
|
||||
|
||||
```
|
||||
# Update and install syncthing:
|
||||
sudo apt-get update
|
||||
sudo apt-get install syncthing
|
||||
```
|
||||
### Post Install
|
||||
Run syncthing as a system service.
|
||||
```
|
||||
sudo systemctl start syncthing@yourusername
|
||||
```
|
||||
|
||||
```
|
||||
sudo systemctl enable syncthing@yourusername
|
||||
```
|
||||
## [Docker](https://www.docker.com/)
|
||||
Docker helps developers build, share, run, and verify applications anywhere — without tedious environment configuration or management.
|
||||
```
|
||||
# Add Docker's official GPG key:
|
||||
sudo apt-get update
|
||||
sudo apt-get install ca-certificates curl
|
||||
sudo install -m 0755 -d /etc/apt/keyrings
|
||||
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
|
||||
sudo chmod a+r /etc/apt/keyrings/docker.asc
|
||||
|
||||
# Add the repository to Apt sources:
|
||||
echo \
|
||||
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
|
||||
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
|
||||
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
sudo apt-get update
|
||||
```
|
||||
|
||||
```
|
||||
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
```
|
||||
|
||||
### Update Users
|
||||
```
|
||||
sudo groupadd docker
|
||||
```
|
||||
|
||||
```
|
||||
sudo usermod -aG docker $USER
|
||||
```
|
||||
|
||||
```
|
||||
newgrp docker
|
||||
```
|
||||
|
||||
### Enable on Boot
|
||||
```
|
||||
sudo systemctl enable docker.service
|
||||
sudo systemctl enable containerd.service
|
||||
```
|
||||
## [Cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/)
|
||||
Connect, protect, and build everywhere. We make websites, apps, and networks faster and more secure. Our developer platform is the best place to build modern apps and deliver AI initiatives.
|
||||
|
||||
```
|
||||
sudo mkdir -p --mode=0755 /usr/share/keyrings
|
||||
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
|
||||
```
|
||||
|
||||
```
|
||||
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main" | sudo tee /etc/apt/sources.list.d/cloudflared.list
|
||||
```
|
||||
|
||||
```
|
||||
sudo apt-get update && sudo apt-get install cloudflared
|
||||
```
|
||||
### Post Install
|
||||
Login to Cloudflare
|
||||
```
|
||||
cloudflared login
|
||||
```
|
||||
### Configuration
|
||||
- Setting up the [[service.cloudflared]] enables your cloudflared tunnel to automatically run on reboot
|
||||
## [Pandoc](https://pandoc.org/)
|
||||
If you need to convert files from one markup format into another, pandoc is your swiss-army knife.
|
||||
|
||||
```
|
||||
sudo apt install pandoc
|
||||
```
|
||||
## [Changemaker (V3.9.9.1)](https://changemaker.bnkops.com)
|
||||
|
||||
@@ -1,6 +1,42 @@
|
||||
# Getting Started with Digital Liberation 🏳️⚧️
|
||||
# Getting Started with Digital Liberation
|
||||
|
||||
Welcome to Changemaker-Lite! You're about to reclaim your digital sovereignty and stop feeding your secrets to corporations. This guide will help you set up your own political infrastructure that you actually own and control.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Build Changemkaer-Lite
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://gitea.bnkops.com/admin/changemaker.lite
|
||||
cd changemaker.lite
|
||||
```
|
||||
|
||||
!!! warning "Cloudflare Credentials"
|
||||
The config.sh script will ask you for your Cloudflare credentials to get started. You can find more information on how to find this in the [Cloudlflare Configuration](./config/cloudflare-config.md)
|
||||
|
||||
|
||||
```
|
||||
# Configure environment (creates .env file)
|
||||
./config.sh
|
||||
```
|
||||
|
||||
```
|
||||
# Start all services
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Deploy
|
||||
|
||||
!!! note "Cloudflare"
|
||||
Right now, we suggest deploying using Cloudflare for simplicity and protections against 99% of surface level attacks to digital infrastructure. If you want to avoid using this service, we recommend checking out [Pagolin](https://github.com/fosrl/pangolin) as a drop in replacement.
|
||||
|
||||
For secure public access, use the production deployment script:
|
||||
|
||||
```bash
|
||||
./start-production.sh
|
||||
```
|
||||
|
||||
Welcome to Changemaker Lite! You're about to reclaim your digital sovereignty and stop feeding your secrets to corporations. This guide will help you set up your own political infrastructure that you actually own and control.
|
||||
|
||||
## Why Changemaker Lite?
|
||||
|
||||
@@ -30,8 +66,13 @@ Before we dive into the technical setup, let's be clear about what you're doing
|
||||
### Operating System
|
||||
|
||||
- **Ubuntu 24.04 LTS (Noble Numbat)** - Recommended and tested
|
||||
|
||||
!!! note "Getting Started on Ubunut"
|
||||
Want some help getting started with a baseline buildout for a Ubunut server? You can use our [BNKops Server Build Script](./build-server.md)
|
||||
|
||||
- Other Linux distributions with systemd support
|
||||
- WSL2 on Windows (limited functionality)
|
||||
- Mac OS
|
||||
|
||||
!!! tip "New to Linux?"
|
||||
Consider [Linux Mint](https://www.linuxmint.com/) - it looks like Windows but opens the door to true digital freedom.
|
||||
@@ -44,10 +85,13 @@ Before we dive into the technical setup, let's be clear about what you're doing
|
||||
- **Network**: Stable internet connection
|
||||
|
||||
!!! info "Cloud Hosting"
|
||||
You can run this on a VPS from providers like Hetzner, DigitalOcean, or Linode for $20-50/month.
|
||||
You can run this on a VPS from providers like Hetzner, DigitalOcean, or Linode for ~$20/month.
|
||||
|
||||
### Software Prerequisites
|
||||
|
||||
!!! tip "Getting Started on Docker"
|
||||
Want some help getting started with a baseline buildout for a Ubunutu server? You can use our [BNKops Server Build Script](./build-server.md) to roll out a configured server in about 20 mins!
|
||||
|
||||
1. **Docker Engine** (24.0+)
|
||||
|
||||
```bash
|
||||
@@ -159,34 +203,29 @@ Expected output should show all services as "Up":
|
||||
|
||||
Once services are running, access them locally:
|
||||
|
||||
### 🏠 Homepage Dashboard
|
||||
|
||||
- **URL**: http://localhost:3010
|
||||
- **Purpose**: Central hub for all services
|
||||
### 🏠 Homepage Dashboard
|
||||
- **URL**: [http://localhost:3010](http://localhost:3010)
|
||||
- **Purpose**: Central hub for all services
|
||||
- **Features**: Service status, quick links, monitoring
|
||||
|
||||
### 💻 Development Tools
|
||||
### 💻 Development Tools
|
||||
- **Code Server**: [http://localhost:8888](http://localhost:8888) — VS Code in browser
|
||||
- **Gitea**: [http://localhost:3030](http://localhost:3030) — Git repository management
|
||||
- **MkDocs Dev**: [http://localhost:4000](http://localhost:4000) — Live documentation preview
|
||||
- **MkDocs Prod**: [http://localhost:4001](http://localhost:4001) — Built documentation
|
||||
|
||||
- **Code Server**: http://localhost:8888 - VS Code in browser
|
||||
- **Gitea**: http://localhost:3030 - Git repository management
|
||||
- **MkDocs Dev**: http://localhost:4000 - Live documentation preview
|
||||
- **MkDocs Prod**: http://localhost:4001 - Built documentation
|
||||
### 📧 Communication
|
||||
- **Listmonk**: [http://localhost:9000](http://localhost:9000) — Email campaigns
|
||||
_Login with credentials set during configuration_
|
||||
|
||||
### 📧 Communication
|
||||
### 🔄 Automation & Data
|
||||
- **n8n**: [http://localhost:5678](http://localhost:5678) — Workflow automation
|
||||
_Login with credentials set during configuration_
|
||||
- **NocoDB**: [http://localhost:8090](http://localhost:8090) — No-code database
|
||||
|
||||
- **Listmonk**: http://localhost:9000 - Email campaigns
|
||||
- Login with credentials set during configuration
|
||||
|
||||
### 🔄 Automation & Data
|
||||
|
||||
- **n8n**: http://localhost:5678 - Workflow automation
|
||||
- Login with credentials set during configuration
|
||||
- **NocoDB**: http://localhost:8090 - No-code database
|
||||
|
||||
### 🛠️ Interactive Tools
|
||||
|
||||
- **Map Viewer**: http://localhost:3000 - Geographic data visualization
|
||||
- **Mini QR**: http://localhost:8089 - QR code generator
|
||||
### 🛠️ Interactive Tools
|
||||
- **Map Viewer**: [http://localhost:3000](http://localhost:3000) — Geographic data visualization
|
||||
- **Mini QR**: [http://localhost:8089](http://localhost:8089) — QR code generator
|
||||
|
||||
## Production Deployment
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# Cloudflare
|
||||
|
||||
In this section, you will find instructions on how to properly set up Cloudflare for managing a Changemaker-lite.
|
||||
|
||||
## Config
|
||||
|
||||
When you first run the `./config.sh` it will prompt you for the a api token, zone id, and a tunnel id. A
|
||||
57
mkdocs/docs/config/cloudflare-config.md
Normal file
57
mkdocs/docs/config/cloudflare-config.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Configure Cloudflare
|
||||
|
||||
Cloudflare is the largest DNS routing service on the planet. We use their free service tier to provide Changemaker users with a fast, secure, and reliable way to get online that blocks 99% of surface level attacks and has built in user authenticaion (if you so choose to use it)
|
||||
|
||||
## Credentials
|
||||
|
||||
The `config.sh` and `start-production.sh` scripts require the following Cloudflare credentials to function properly:
|
||||
|
||||
### 1. **Cloudflare API Token**
|
||||
|
||||
- **Purpose**: Used to authenticate API requests to Cloudflare for managing DNS records, tunnels, and access policies.
|
||||
- **Required Permissions**:
|
||||
- `Zone.DNS` (Read/Write)
|
||||
- `Account.Cloudflare Tunnel` (Read/Write)
|
||||
- **How to Obtain**:
|
||||
- Log in to your Cloudflare account.
|
||||
- Go to **My Profile** > **API Tokens** > **Create Token**.
|
||||
- Use the **Edit zone DNS** template and add **Cloudflare Tunnel** permissions.
|
||||
|
||||
### 2. **Cloudflare Zone ID**
|
||||
|
||||
- **Purpose**: Identifies the specific DNS zone (domain) in Cloudflare where DNS records will be created.
|
||||
- **How to Obtain**:
|
||||
- Log in to your Cloudflare account.
|
||||
- Select the domain you want to use.
|
||||
- The Zone ID is displayed in the **Overview** section under **API**.
|
||||
|
||||
### 3. **Cloudflare Account ID**
|
||||
|
||||
- **Purpose**: Identifies your Cloudflare account for tunnel creation and management.
|
||||
- **How to Obtain**:
|
||||
- Log in to your Cloudflare account.
|
||||
- Go to **My Profile** > **API Tokens**.
|
||||
- The Account ID is displayed at the top of the page.
|
||||
|
||||
### 4. **Cloudflare Tunnel ID** (Optional in config.sh, Required in start-production.sh)
|
||||
|
||||
- **Purpose**: Identifies the specific Cloudflare Tunnel that will be used to route traffic to your services.
|
||||
- **How to Obtain**:
|
||||
- This is automatically generated when you create a tunnel using `cloudflared tunnel create` or via the Cloudflare dashboard.
|
||||
- The start-production.sh script will create this for you if it doesn't exist.
|
||||
|
||||
### Summary of Required Credentials:
|
||||
|
||||
```bash
|
||||
# In .env file
|
||||
CF_API_TOKEN=your_cloudflare_api_token
|
||||
CF_ZONE_ID=your_cloudflare_zone_id
|
||||
CF_ACCOUNT_ID=your_cloudflare_account_id
|
||||
CF_TUNNEL_ID=will_be_set_by_start_production # This will be set by start-production.sh
|
||||
```
|
||||
|
||||
### Notes:
|
||||
|
||||
- The config.sh script will prompt you for these credentials during setup.
|
||||
- The start-production.sh script will verify these credentials and use them to configure DNS records, create tunnels, and set up access policies.
|
||||
- Ensure that the API token has the correct permissions, or the scripts will fail to configure Cloudflare services.
|
||||
6
mkdocs/docs/config/index.md
Normal file
6
mkdocs/docs/config/index.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# Configuration
|
||||
|
||||
There are several configuration steps to building a production ready Changemaker-Lite.
|
||||
|
||||
In the order we suggest doing them:
|
||||
|
||||
BIN
mkdocs/docs/hooks/__pycache__/repo_widget_hook.cpython-311.pyc
Normal file
BIN
mkdocs/docs/hooks/__pycache__/repo_widget_hook.cpython-311.pyc
Normal file
Binary file not shown.
132
mkdocs/docs/hooks/repo_widget_hook.py
Normal file
132
mkdocs/docs/hooks/repo_widget_hook.py
Normal file
@@ -0,0 +1,132 @@
|
||||
"""
|
||||
MkDocs Hook for Repository Widget Data Generation
|
||||
Fetches repository data and generates JSON files during build
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any
|
||||
import logging
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def on_pre_build(config: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Hook that runs before MkDocs builds the site
|
||||
Generates repository JSON data files
|
||||
"""
|
||||
logger.info("Generating repository widget data...")
|
||||
|
||||
# Define repositories to fetch - all repos from services.md
|
||||
repositories = [
|
||||
# Gitea repositories
|
||||
{
|
||||
"repo": "admin/changemaker.lite",
|
||||
"gitea_url": "https://gitea.bnkops.com",
|
||||
"token": os.getenv("GITEA_TOKEN")
|
||||
},
|
||||
# GitHub repositories
|
||||
{
|
||||
"repo": "coder/code-server",
|
||||
"github": True
|
||||
},
|
||||
{
|
||||
"repo": "n8n-io/n8n",
|
||||
"github": True
|
||||
},
|
||||
{
|
||||
"repo": "knadh/listmonk",
|
||||
"github": True
|
||||
},
|
||||
{
|
||||
"repo": "nocodb/nocodb",
|
||||
"github": True
|
||||
},
|
||||
{ "repo": "squidfunk/mkdocs-material",
|
||||
"github": True
|
||||
},
|
||||
{
|
||||
"repo": "gethomepage/homepage",
|
||||
"github": True
|
||||
}
|
||||
]
|
||||
|
||||
# Create output directory
|
||||
docs_dir = config.get('docs_dir', 'docs')
|
||||
output_dir = Path(docs_dir) / 'assets' / 'repo-data'
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Generate data for each repository
|
||||
for repo_config in repositories:
|
||||
try:
|
||||
generate_repo_data(repo_config, output_dir)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to generate data for {repo_config['repo']}: {e}")
|
||||
|
||||
def generate_repo_data(repo_config: Dict[str, Any], output_dir: Path) -> None:
|
||||
"""Generate JSON data file for a single repository"""
|
||||
|
||||
repo = repo_config['repo']
|
||||
logger.info(f"Fetching data for {repo}")
|
||||
|
||||
# Determine API URL and headers
|
||||
if repo_config.get('github'):
|
||||
api_url = f"https://api.github.com/repos/{repo}"
|
||||
headers = {'Accept': 'application/vnd.github.v3+json'}
|
||||
github_token = os.getenv('GITHUB_TOKEN')
|
||||
if github_token:
|
||||
headers['Authorization'] = f'token {github_token}'
|
||||
else:
|
||||
gitea_url = repo_config.get('gitea_url', 'https://gitea.bnkops.com')
|
||||
api_url = f"{gitea_url}/api/v1/repos/{repo}"
|
||||
headers = {'Accept': 'application/json'}
|
||||
token = repo_config.get('token') or os.getenv('GITEA_TOKEN')
|
||||
if token:
|
||||
headers['Authorization'] = f'token {token}'
|
||||
|
||||
# Fetch repository data
|
||||
try:
|
||||
response = requests.get(api_url, headers=headers, timeout=10)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
except requests.RequestException as e:
|
||||
logger.error(f"Failed to fetch {repo}: {e}")
|
||||
return
|
||||
|
||||
# Extract widget data
|
||||
widget_data = {
|
||||
'full_name': data.get('full_name'),
|
||||
'name': data.get('name'),
|
||||
'description': data.get('description'),
|
||||
'html_url': data.get('html_url'),
|
||||
'language': data.get('language'),
|
||||
'stars_count': data.get('stargazers_count') or data.get('stars_count', 0),
|
||||
'forks_count': data.get('forks_count', 0),
|
||||
'open_issues_count': data.get('open_issues_count', 0),
|
||||
'updated_at': data.get('updated_at'),
|
||||
'created_at': data.get('created_at'),
|
||||
'clone_url': data.get('clone_url'),
|
||||
'ssh_url': data.get('ssh_url'),
|
||||
'default_branch': data.get('default_branch', 'main'),
|
||||
'last_build_update': data.get('pushed_at') or data.get('updated_at')
|
||||
}
|
||||
|
||||
# Generate filename
|
||||
filename = f"{repo.replace('/', '-')}.json"
|
||||
file_path = output_dir / filename
|
||||
|
||||
# Write JSON file
|
||||
try:
|
||||
with open(file_path, 'w') as f:
|
||||
json.dump(widget_data, f, indent=2)
|
||||
logger.info(f"Generated: {filename}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to write {filename}: {e}")
|
||||
|
||||
def on_post_build(config: Dict[str, Any]) -> None:
|
||||
"""Hook that runs after MkDocs builds the site"""
|
||||
logger.info("Repository widget data generation complete")
|
||||
132
mkdocs/docs/javascripts/gitea-widget.js
Normal file
132
mkdocs/docs/javascripts/gitea-widget.js
Normal file
@@ -0,0 +1,132 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const widgets = document.querySelectorAll('.gitea-widget');
|
||||
|
||||
widgets.forEach(widget => {
|
||||
const repo = widget.dataset.repo;
|
||||
|
||||
// Auto-generate data file path from repo name
|
||||
const dataFile = `/assets/repo-data/${repo.replace('/', '-')}.json`;
|
||||
const showDescription = widget.dataset.showDescription !== 'false';
|
||||
const showLanguage = widget.dataset.showLanguage !== 'false';
|
||||
const showLastUpdate = widget.dataset.showLastUpdate !== 'false';
|
||||
|
||||
// Show loading state
|
||||
widget.innerHTML = `
|
||||
<div class="gitea-widget-loading">
|
||||
<div class="loading-spinner"></div>
|
||||
<span>Loading ${repo}...</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Fetch repository data
|
||||
fetch(dataFile)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Could not load data for ${repo}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
renderWidget(widget, data, { showDescription, showLanguage, showLastUpdate });
|
||||
})
|
||||
.catch(error => {
|
||||
renderError(widget, repo, error.message);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function renderWidget(widget, data, options) {
|
||||
const lastUpdate = new Date(data.updated_at).toLocaleDateString();
|
||||
const language = data.language || 'Not specified';
|
||||
|
||||
widget.innerHTML = `
|
||||
<div class="gitea-widget-container">
|
||||
<div class="gitea-widget-header">
|
||||
<div class="gitea-widget-title">
|
||||
<svg class="gitea-icon" viewBox="0 0 16 16" width="16" height="16">
|
||||
<path fill="currentColor" d="M2 2.5A2.5 2.5 0 014.5 0h8.75a.75.75 0 01.75.75v12.5a.75.75 0 01-.75.75h-2.5a.75.75 0 110-1.5h1.75v-2h-8a1 1 0 00-.714 1.7.75.75 0 01-1.072 1.05A2.495 2.495 0 012 11.5v-9zm10.5-1V9h-8c-.356 0-.694.074-1 .208V2.5a1 1 0 011-1h8zM5 12.25v3.25a.25.25 0 00.4.2l1.45-1.087a.25.25 0 01.3 0L8.6 15.7a.25.25 0 00.4-.2v-3.25a.25.25 0 00-.25-.25h-3.5a.25.25 0 00-.25.25z"/>
|
||||
</svg>
|
||||
<a href="${data.html_url}" target="_blank" rel="noopener noreferrer" class="repo-link">
|
||||
${data.full_name}
|
||||
</a>
|
||||
</div>
|
||||
<div class="gitea-widget-stats">
|
||||
<span class="stat-item" title="Stars">
|
||||
<svg viewBox="0 0 16 16" width="14" height="14">
|
||||
<path fill="currentColor" d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25z"/>
|
||||
</svg>
|
||||
${data.stars_count.toLocaleString()}
|
||||
</span>
|
||||
<span class="stat-item" title="Forks">
|
||||
<svg viewBox="0 0 16 16" width="14" height="14">
|
||||
<path fill="currentColor" d="M5 3.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm0 2.122a2.25 2.25 0 10-1.5 0v.878A2.25 2.25 0 005.75 8.5h1.5v2.128a2.251 2.251 0 101.5 0V8.5h1.5a2.25 2.25 0 002.25-2.25V5.372a2.25 2.25 0 10-1.5 0v.878A.75.75 0 0110.25 7H5.75A.75.75 0 015 6.25v-.878zm3.75 7.378a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm3-8.75a.75.75 0 100-1.5.75.75 0 000 1.5z"/>
|
||||
</svg>
|
||||
${data.forks_count.toLocaleString()}
|
||||
</span>
|
||||
<span class="stat-item" title="Open Issues">
|
||||
<svg viewBox="0 0 16 16" width="14" height="14">
|
||||
<path fill="currentColor" d="M8 9.5a1.5 1.5 0 100-3 1.5 1.5 0 000 3z"/>
|
||||
<path fill="currentColor" fill-rule="evenodd" d="M8 0a8 8 0 100 16A8 8 0 008 0zM1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0z"/>
|
||||
</svg>
|
||||
${data.open_issues_count.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
${options.showDescription && data.description ? `
|
||||
<div class="gitea-widget-description">
|
||||
${data.description}
|
||||
</div>
|
||||
` : ''}
|
||||
<div class="gitea-widget-footer">
|
||||
${options.showLanguage ? `
|
||||
<span class="language-info">
|
||||
<span class="language-dot" style="background-color: ${getLanguageColor(language)}"></span>
|
||||
${language}
|
||||
</span>
|
||||
` : ''}
|
||||
${options.showLastUpdate ? `
|
||||
<span class="last-update">Updated ${lastUpdate}</span>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderError(widget, repo, error) {
|
||||
widget.innerHTML = `
|
||||
<div class="gitea-widget-error">
|
||||
<svg viewBox="0 0 16 16" width="16" height="16">
|
||||
<path fill="currentColor" d="M2.343 13.657A8 8 0 1113.657 2.343 8 8 0 012.343 13.657zM6.03 4.97a.75.75 0 00-1.06 1.06L6.94 8 4.97 9.97a.75.75 0 101.06 1.06L8 9.06l1.97 1.97a.75.75 0 101.06-1.06L9.06 8l1.97-1.97a.75.75 0 10-1.06-1.06L8 6.94 6.03 4.97z"/>
|
||||
</svg>
|
||||
<span>Failed to load ${repo}</span>
|
||||
<small>${error}</small>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function getLanguageColor(language) {
|
||||
const colors = {
|
||||
'JavaScript': '#f1e05a',
|
||||
'TypeScript': '#2b7489',
|
||||
'Python': '#3572A5',
|
||||
'Java': '#b07219',
|
||||
'C++': '#f34b7d',
|
||||
'C': '#555555',
|
||||
'C#': '#239120',
|
||||
'PHP': '#4F5D95',
|
||||
'Ruby': '#701516',
|
||||
'Go': '#00ADD8',
|
||||
'Rust': '#dea584',
|
||||
'Swift': '#ffac45',
|
||||
'Kotlin': '#F18E33',
|
||||
'Scala': '#c22d40',
|
||||
'Shell': '#89e051',
|
||||
'HTML': '#e34c26',
|
||||
'CSS': '#563d7c',
|
||||
'Vue': '#2c3e50',
|
||||
'React': '#61dafb',
|
||||
'Dockerfile': '#384d54',
|
||||
'Markdown': '#083fa1'
|
||||
};
|
||||
return colors[language] || '#586069';
|
||||
}
|
||||
167
mkdocs/docs/javascripts/github-widget.js
Normal file
167
mkdocs/docs/javascripts/github-widget.js
Normal file
@@ -0,0 +1,167 @@
|
||||
let widgetsInitialized = new Set();
|
||||
|
||||
function initializeGitHubWidgets() {
|
||||
const widgets = document.querySelectorAll('.github-widget');
|
||||
|
||||
widgets.forEach(widget => {
|
||||
// Skip if already initialized
|
||||
const widgetId = widget.dataset.repo + '-' + Math.random().toString(36).substr(2, 9);
|
||||
if (widget.dataset.initialized) return;
|
||||
|
||||
widget.dataset.initialized = 'true';
|
||||
const repo = widget.dataset.repo;
|
||||
|
||||
// Auto-generate data file path from repo name
|
||||
const dataFile = `/assets/repo-data/${repo.replace('/', '-')}.json`;
|
||||
const showDescription = widget.dataset.showDescription !== 'false';
|
||||
const showLanguage = widget.dataset.showLanguage !== 'false';
|
||||
const showLastUpdate = widget.dataset.showLastUpdate !== 'false';
|
||||
|
||||
// Show loading state
|
||||
widget.innerHTML = `
|
||||
<div class="github-widget-loading">
|
||||
<div class="loading-spinner"></div>
|
||||
<span>Loading ${repo}...</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Fetch repository data from pre-generated JSON file
|
||||
fetch(dataFile)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Could not load data for ${repo}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
const lastUpdate = new Date(data.updated_at).toLocaleDateString();
|
||||
const language = data.language || 'Not specified';
|
||||
|
||||
widget.innerHTML = `
|
||||
<div class="github-widget-container">
|
||||
<div class="github-widget-header">
|
||||
<div class="github-widget-title">
|
||||
<svg class="github-icon" viewBox="0 0 16 16" width="16" height="16">
|
||||
<path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
|
||||
</svg>
|
||||
<a href="${data.html_url}" target="_blank" rel="noopener noreferrer" class="repo-link">
|
||||
${data.full_name}
|
||||
</a>
|
||||
</div>
|
||||
<div class="github-widget-stats">
|
||||
<span class="stat-item" title="Stars">
|
||||
<svg viewBox="0 0 16 16" width="14" height="14">
|
||||
<path fill="currentColor" d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25z"/>
|
||||
</svg>
|
||||
${data.stars_count.toLocaleString()}
|
||||
</span>
|
||||
<span class="stat-item" title="Forks">
|
||||
<svg viewBox="0 0 16 16" width="14" height="14">
|
||||
<path fill="currentColor" d="M5 3.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm0 2.122a2.25 2.25 0 10-1.5 0v.878A2.25 2.25 0 005.75 8.5h1.5v2.128a2.251 2.251 0 101.5 0V8.5h1.5a2.25 2.25 0 002.25-2.25V5.372a2.25 2.25 0 10-1.5 0v.878A.75.75 0 0110.25 7H5.75A.75.75 0 015 6.25v-.878zm3.75 7.378a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm3-8.75a.75.75 0 100-1.5.75.75 0 000 1.5z"/>
|
||||
</svg>
|
||||
${data.forks_count.toLocaleString()}
|
||||
</span>
|
||||
<span class="stat-item" title="Open Issues">
|
||||
<svg viewBox="0 0 16 16" width="14" height="14">
|
||||
<path fill="currentColor" d="M8 9.5a1.5 1.5 0 100-3 1.5 1.5 0 000 3z"/>
|
||||
<path fill="currentColor" fill-rule="evenodd" d="M8 0a8 8 0 100 16A8 8 0 008 0zM1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0z"/>
|
||||
</svg>
|
||||
${data.open_issues_count.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
${showDescription && data.description ? `
|
||||
<div class="github-widget-description">
|
||||
${data.description}
|
||||
</div>
|
||||
` : ''}
|
||||
<div class="github-widget-footer">
|
||||
${showLanguage ? `
|
||||
<span class="language-info">
|
||||
<span class="language-dot" style="background-color: ${getLanguageColor(language)}"></span>
|
||||
${language}
|
||||
</span>
|
||||
` : ''}
|
||||
${showLastUpdate ? `
|
||||
<span class="last-update">Updated ${lastUpdate}</span>
|
||||
` : ''}
|
||||
${data.license ? `
|
||||
<span class="license-info">${data.license.spdx_id || data.license.name}</span>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading repository data:', error);
|
||||
widget.innerHTML = `
|
||||
<div class="github-widget-error">
|
||||
<svg viewBox="0 0 16 16" width="16" height="16">
|
||||
<path fill="currentColor" d="M2.343 13.657A8 8 0 1113.657 2.343 8 8 0 012.343 13.657zM6.03 4.97a.75.75 0 00-1.06 1.06L6.94 8 4.97 9.97a.75.75 0 101.06 1.06L8 9.06l1.97 1.97a.75.75 0 101.06-1.06L9.06 8l1.97-1.97a.75.75 0 10-1.06-1.06L8 6.94 6.03 4.97z"/>
|
||||
</svg>
|
||||
<span>Failed to load ${repo}</span>
|
||||
<small>${error.message}</small>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize on DOM ready
|
||||
document.addEventListener('DOMContentLoaded', initializeGitHubWidgets);
|
||||
|
||||
// Watch for DOM changes (MkDocs Material dynamic content)
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
let shouldReinitialize = false;
|
||||
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.type === 'childList') {
|
||||
// Check if any github-widget elements were added
|
||||
mutation.addedNodes.forEach((node) => {
|
||||
if (node.nodeType === 1 && // Element node
|
||||
(node.classList?.contains('github-widget') ||
|
||||
node.querySelector?.('.github-widget'))) {
|
||||
shouldReinitialize = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (shouldReinitialize) {
|
||||
// Small delay to ensure DOM is stable
|
||||
setTimeout(initializeGitHubWidgets, 100);
|
||||
}
|
||||
});
|
||||
|
||||
// Start observing
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
|
||||
// Language color mapping (simplified version)
|
||||
function getLanguageColor(language) {
|
||||
const colors = {
|
||||
'JavaScript': '#f1e05a',
|
||||
'TypeScript': '#2b7489',
|
||||
'Python': '#3572A5',
|
||||
'Java': '#b07219',
|
||||
'C++': '#f34b7d',
|
||||
'C': '#555555',
|
||||
'C#': '#239120',
|
||||
'PHP': '#4F5D95',
|
||||
'Ruby': '#701516',
|
||||
'Go': '#00ADD8',
|
||||
'Rust': '#dea584',
|
||||
'Swift': '#ffac45',
|
||||
'Kotlin': '#F18E33',
|
||||
'Scala': '#c22d40',
|
||||
'Shell': '#89e051',
|
||||
'HTML': '#e34c26',
|
||||
'CSS': '#563d7c',
|
||||
'Vue': '#2c3e50',
|
||||
'React': '#61dafb',
|
||||
'Dockerfile': '#384d54'
|
||||
};
|
||||
return colors[language] || '#586069';
|
||||
}
|
||||
@@ -1,339 +1,369 @@
|
||||
{% extends "main.html" %}
|
||||
|
||||
{% block extrahead %}
|
||||
{{ super() }}
|
||||
<script>
|
||||
{{ super() }}
|
||||
<script>
|
||||
// Add data attribute for CSS targeting
|
||||
document.body.setAttribute('data-md-template', 'home');
|
||||
</script>
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="md-content--home">
|
||||
|
||||
<!-- Hero Grid Section -->
|
||||
<section class="hero-grid">
|
||||
<div class="grid-container">
|
||||
<!-- Main Hero Block -->
|
||||
<div class="hero-main grid-card">
|
||||
<div class="meta-badge">🤯 This Site Runs On Changemaker Lite</div>
|
||||
<h1>The Only FOSS Political Campaign Stack</h1>
|
||||
<p>Stop paying $1,200/month to corporate platforms. Run your entire political campaign on $200 hardware with fewer corporate dependencies every day.</p>
|
||||
|
||||
<div class="hero-ctas">
|
||||
<a href="#solution" class="btn btn-primary">See The Solution</a>
|
||||
<a href="#get-started" class="btn btn-secondary">Get Started $200</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Problem Statement Block -->
|
||||
<div class="hero-problem grid-card">
|
||||
<h3>The Problem</h3>
|
||||
<div class="problem-list">
|
||||
<div class="problem-item">💸 NGP VAN: $1,000+/mo</div>
|
||||
<div class="problem-item">📧 Mailchimp: $200+/mo</div>
|
||||
<div class="problem-item">🔒 Your data: Their servers</div>
|
||||
<div class="problem-item">👁️ Surveillance: Always on</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Solution Stats Block -->
|
||||
<div class="hero-stats grid-card">
|
||||
<h3>The Solution</h3>
|
||||
<div class="stat-grid">
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">$200</div>
|
||||
<div class="stat-label">One-time cost</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">11</div>
|
||||
<div class="stat-label">Campaign tools</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">100%</div>
|
||||
<div class="stat-label">Your data</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">↓</div>
|
||||
<div class="stat-label">Fewer corporate deps every day</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Trust Indicators -->
|
||||
<div class="hero-trust grid-card">
|
||||
<h3>Proven By</h3>
|
||||
<div class="trust-items">
|
||||
<div class="trust-item">
|
||||
<span class="trust-icon">⚡</span>
|
||||
<span class="trust-text">Powers BNKops.com</span>
|
||||
</div>
|
||||
<div class="trust-item">
|
||||
<span class="trust-icon">🏳️⚧️</span>
|
||||
<span class="trust-text">Trans liberation tech</span>
|
||||
</div>
|
||||
<div class="trust-item">
|
||||
<span class="trust-icon">🛡️</span>
|
||||
<span class="trust-text">Zero surveillance</span>
|
||||
</div>
|
||||
<div class="trust-item">
|
||||
<span class="trust-icon">🚀</span>
|
||||
<span class="trust-text">30-min setup</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Hero Grid Section -->
|
||||
<section class="hero-grid">
|
||||
<div class="grid-container">
|
||||
<!-- Main Hero Block -->
|
||||
<div class="hero-main grid-card">
|
||||
<div class="meta-badge">🤯 This Site Runs On Changemaker Lite</div>
|
||||
<h1>The Only Free and Open Source Political Campaign Stack</h1>
|
||||
<p>Stop paying thousands of dollars a month to corporate platforms just to be surveilled and your supports data sold to the highest bidder. Run your entire political campaign on ~$200 hardware and
|
||||
with fewer corporate dependencies every day.</p>
|
||||
|
||||
<!-- Solution Grid -->
|
||||
<section class="solution-grid" id="solution">
|
||||
<div class="grid-container">
|
||||
<div class="section-header">
|
||||
<h2>Everything Your Campaign Needs</h2>
|
||||
<p>Replace your entire SaaS stack with open source alternatives. <b>First free and open source canvassing solution included.</b></p>
|
||||
</div>
|
||||
|
||||
<div class="services-grid">
|
||||
<!-- Code Server -->
|
||||
<div class="service-card grid-card">
|
||||
<div class="service-icon">💻</div>
|
||||
<h3>Code Server</h3>
|
||||
<div class="service-replaces">Replaces: VS Code</div>
|
||||
<ul class="service-features">
|
||||
<li>Browser-based IDE</li>
|
||||
<li>Customizable workspace</li>
|
||||
<li>Live collaboration</li>
|
||||
</ul>
|
||||
<div class="service-tool">Code Server</div>
|
||||
<div class="hero-ctas">
|
||||
<a href="#solution" class="btn btn-primary">See The Solution</a>
|
||||
<a href="#get-started" class="btn btn-secondary">Get Started</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MkDocs -->
|
||||
<div class="service-card grid-card">
|
||||
<div class="service-icon">📖</div>
|
||||
<h3>Documentation</h3>
|
||||
<div class="service-replaces">Replaces: Confluence</div>
|
||||
<ul class="service-features">
|
||||
<li>Markdown-based</li>
|
||||
<li>Live preview</li>
|
||||
<li>Static site generation</li>
|
||||
</ul>
|
||||
<div class="service-tool">MkDocs</div>
|
||||
</div>
|
||||
|
||||
<!-- Listmonk -->
|
||||
<div class="service-card grid-card">
|
||||
<div class="service-icon">📧</div>
|
||||
<h3>Email Campaigns</h3>
|
||||
<div class="service-replaces">Replaces: Mailchimp</div>
|
||||
<ul class="service-features">
|
||||
<li>Unlimited subscribers</li>
|
||||
<li>A/B testing</li>
|
||||
<li>Analytics</li>
|
||||
</ul>
|
||||
<div class="service-tool">Listmonk</div>
|
||||
</div>
|
||||
|
||||
<!-- NocoDB -->
|
||||
<div class="service-card grid-card">
|
||||
<div class="service-icon">📊</div>
|
||||
<h3>Database & Canvassing</h3>
|
||||
<div class="service-replaces">Replaces: Airtable</div>
|
||||
<ul class="service-features">
|
||||
<li>Contact management</li>
|
||||
<li>Custom fields</li>
|
||||
<li>Canvassing support</li>
|
||||
</ul>
|
||||
<div class="service-tool">NocoDB</div>
|
||||
</div>
|
||||
|
||||
<!-- Map Viewer -->
|
||||
<div class="service-card grid-card">
|
||||
<div class="service-icon">🗺️</div>
|
||||
<h3>Interactive Maps</h3>
|
||||
<div class="service-replaces">Replaces: Google Maps</div>
|
||||
<ul class="service-features">
|
||||
<li>Real-time geolocation</li>
|
||||
<li>Custom map layers</li>
|
||||
<li>Location management</li>
|
||||
</ul>
|
||||
<div class="service-tool">Map Viewer</div>
|
||||
</div>
|
||||
|
||||
<!-- Automation -->
|
||||
<div class="service-card grid-card">
|
||||
<div class="service-icon">🔄</div>
|
||||
<h3>Workflow Automation</h3>
|
||||
<div class="service-replaces">Replaces: Zapier</div>
|
||||
<ul class="service-features">
|
||||
<li>Workflow builder</li>
|
||||
<li>API connections</li>
|
||||
<li>Event triggers</li>
|
||||
</ul>
|
||||
<div class="service-tool">n8n</div>
|
||||
</div>
|
||||
|
||||
<!-- QR Code Generator -->
|
||||
<div class="service-card grid-card">
|
||||
<div class="service-icon">📱</div>
|
||||
<h3>QR Code Generator</h3>
|
||||
<div class="service-replaces">Replaces: QR Code SaaS</div>
|
||||
<ul class="service-features">
|
||||
<li>Customizable codes</li>
|
||||
<li>Batch generation</li>
|
||||
<li>Static and dynamic codes</li>
|
||||
</ul>
|
||||
<div class="service-tool">Mini QR</div>
|
||||
</div>
|
||||
|
||||
<!-- Gitea -->
|
||||
<div class="service-card grid-card">
|
||||
<div class="service-icon">🔧</div>
|
||||
<h3>Version Control</h3>
|
||||
<div class="service-replaces">Replaces: GitHub</div>
|
||||
<ul class="service-features">
|
||||
<li>Self-hosted Git</li>
|
||||
<li>Collaborative development</li>
|
||||
<li>Issue tracking</li>
|
||||
</ul>
|
||||
<div class="service-tool">Gitea</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Comparison Grid -->
|
||||
<section class="comparison-grid">
|
||||
<div class="grid-container">
|
||||
<div class="section-header">
|
||||
<h2>Corporate SaaS vs Changemaker Lite</h2>
|
||||
<p>Why campaigns are switching to self-hosted FOSS</p>
|
||||
</div>
|
||||
|
||||
<div class="comparison-table">
|
||||
<div class="comparison-header grid-card">
|
||||
<div></div>
|
||||
<div class="compare-col">Corporate SaaS</div>
|
||||
<div class="compare-col highlight">Changemaker Lite</div>
|
||||
</div>
|
||||
|
||||
<div class="comparison-row grid-card">
|
||||
<div class="compare-label">Monthly Cost</div>
|
||||
<div class="compare-value bad">$1,200+</div>
|
||||
<div class="compare-value good">$50 hosting</div>
|
||||
</div>
|
||||
|
||||
<div class="comparison-row grid-card">
|
||||
<div class="compare-label">Data Ownership</div>
|
||||
<div class="compare-value bad">Their servers</div>
|
||||
<div class="compare-value good">100% yours</div>
|
||||
</div>
|
||||
|
||||
<div class="comparison-row grid-card">
|
||||
<div class="compare-label">Surveillance</div>
|
||||
<div class="compare-value bad">Always tracked</div>
|
||||
<div class="compare-value good">Zero tracking</div>
|
||||
</div>
|
||||
|
||||
<div class="comparison-row grid-card">
|
||||
<div class="compare-label">Vendor Lock-in</div>
|
||||
<div class="compare-value bad">Trapped forever</div>
|
||||
<div class="compare-value good">Export anytime</div>
|
||||
</div>
|
||||
|
||||
<div class="comparison-row grid-card">
|
||||
<div class="compare-label">Customization</div>
|
||||
<div class="compare-value bad">Limited options</div>
|
||||
<div class="compare-value good">Full control</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Get Started Grid -->
|
||||
<section class="get-started-grid" id="get-started">
|
||||
<div class="grid-container">
|
||||
<div class="section-header">
|
||||
<h2>Start Your Digital Liberation</h2>
|
||||
<p>Three ways to break free from corporate campaign tech</p>
|
||||
</div>
|
||||
|
||||
<div class="options-grid">
|
||||
<!-- Plug & Play -->
|
||||
<div class="option-card grid-card featured">
|
||||
<div class="option-badge">Most Popular</div>
|
||||
<div class="option-icon">🚀</div>
|
||||
<h3>Plug & Play</h3>
|
||||
<div class="option-price">$200</div>
|
||||
<div class="option-features">
|
||||
<div class="feature">✓ Pre-configured hardware</div>
|
||||
<div class="feature">✓ 30-minute setup</div>
|
||||
<div class="feature">✓ All 11 services ready</div>
|
||||
<div class="feature">✓ Priority support</div>
|
||||
<div class="feature">✓ Ships on request</div>
|
||||
<!-- Problem Statement Block -->
|
||||
<div class="hero-problem grid-card">
|
||||
<h3>The Problem</h3>
|
||||
<div class="problem-list">
|
||||
<div class="problem-item">💸 Canvassing: $100+/mo</div>
|
||||
<div class="problem-item">📊 Nationbuilder: $500+/mo</div>
|
||||
<div class="problem-item">📧 Mailchimp: $200+/mo</div>
|
||||
<div class="problem-item">👁️ Surveillance: Always on</div>
|
||||
<div class="problem-item">🔒 Vendor lock-in: Trapped forever</div>
|
||||
<div class="problem-item">📉 Data ownership: Not yours</div>
|
||||
<div class="promlem-itme">😠 Implicitly Supporting Evil Corporations</div>
|
||||
</div>
|
||||
<a href="https://bnkops.com/hardware" class="btn btn-primary">Order Hardware</a>
|
||||
</div>
|
||||
|
||||
<!-- DIY -->
|
||||
<div class="option-card grid-card">
|
||||
<div class="option-icon">🛠️</div>
|
||||
<h3>DIY Install</h3>
|
||||
<div class="option-price">Free</div>
|
||||
<div class="option-features">
|
||||
<div class="feature">✓ Use your hardware</div>
|
||||
<div class="feature">✓ Docker compose</div>
|
||||
<div class="feature">✓ Full documentation</div>
|
||||
<div class="feature">✓ Community support</div>
|
||||
<div class="feature">✓ Learn the system</div>
|
||||
|
||||
<!-- Solution Stats Block -->
|
||||
<div class="hero-stats grid-card">
|
||||
<h3>The Solution</h3>
|
||||
<div class="stat-grid">
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">Runs on ~$200 Hardware</div>
|
||||
<div class="stat-label">One-time cost</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">11</div>
|
||||
<div class="stat-label">Campaign tools</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">Your Data</div>
|
||||
<div class="stat-label">Localized & Secure</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">↓</div>
|
||||
<div class="stat-label">Fewer corporate deps every day</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/getting-started/" class="btn btn-secondary">Install Guide</a>
|
||||
</div>
|
||||
|
||||
<!-- Managed -->
|
||||
<div class="option-card grid-card">
|
||||
<div class="option-icon">☁️</div>
|
||||
<h3>Managed Hosting</h3>
|
||||
<div class="option-price">$???/mo</div>
|
||||
<div class="option-features">
|
||||
<div class="feature">✓ We manage everything</div>
|
||||
<div class="feature">✓ Your domain</div>
|
||||
<div class="feature">✓ Daily backups</div>
|
||||
<div class="feature">✓ 24/7 monitoring</div>
|
||||
<div class="feature">✓ Still your data</div>
|
||||
|
||||
<!-- Trust Indicators -->
|
||||
<div class="hero-trust grid-card">
|
||||
<h3>Proven By</h3>
|
||||
<div class="trust-items">
|
||||
<div class="trust-item">
|
||||
<span class="trust-icon">⚡</span>
|
||||
<span class="trust-text">Powers BNKops.com</span>
|
||||
</div>
|
||||
<div class="trust-item">
|
||||
<span class="trust-icon"> 🌈 </span>
|
||||
<span class="trust-text">Liberation tech</span>
|
||||
</div>
|
||||
<div class="trust-item">
|
||||
<span class="trust-icon">🛡️</span>
|
||||
<span class="trust-text">Zero surveillance</span>
|
||||
</div>
|
||||
<div class="trust-item">
|
||||
<span class="trust-icon">🚀</span>
|
||||
<span class="trust-text">30-min setup</span>
|
||||
</div>
|
||||
</div>
|
||||
<a href="https://bnkops.com/hosting" class="btn btn-secondary">Learn More</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Final CTA -->
|
||||
<div class="final-cta grid-card">
|
||||
<h3>Join the Movement</h3>
|
||||
<p>Some of the biggest players in local politics are already using it; maybe give it a try?</p>
|
||||
<div class="cta-buttons">
|
||||
<a href="https://bnkops.com/newsletter" class="btn btn-primary">Get Campaign Tips</a>
|
||||
<a href="https://docs.bnkops.com" class="btn btn-secondary">Read Documentation</a>
|
||||
</section>
|
||||
|
||||
<!-- Solution Grid -->
|
||||
<section class="solution-grid" id="solution">
|
||||
<div class="grid-container">
|
||||
<div class="section-header">
|
||||
<h2>Your Complete Campaign Toolkit</h2>
|
||||
<p>Simple, powerful tools designed for campaign staff - no technical skills needed</p>
|
||||
</div>
|
||||
|
||||
<div class="services-grid">
|
||||
<!-- Documentation Editor -->
|
||||
<div class="service-card grid-card">
|
||||
<div class="service-icon">✍️</div>
|
||||
<h3>Campaign Content Editor</h3>
|
||||
<div class="service-replaces">Replaces: Google Docs + WordPress (~$50/month)</div>
|
||||
<ul class="service-features">
|
||||
<li>Edit docs & blog posts</li>
|
||||
<li>Live preview as you type</li>
|
||||
<li>Simple formatting tools</li>
|
||||
<li>Works in any browser</li>
|
||||
</ul>
|
||||
<div class="service-tool">Code Server</div>
|
||||
</div>
|
||||
|
||||
<!-- Canvassing System -->
|
||||
<div class="service-card grid-card featured">
|
||||
<div class="service-badge">Field Team Essential</div>
|
||||
<div class="service-icon">🚪</div>
|
||||
<h3>Smart Canvassing</h3>
|
||||
<div class="service-replaces">Replaces: Qumon (~$100+/mo)</div>
|
||||
<ul class="service-features">
|
||||
<li>Interactive voter maps</li>
|
||||
<li>Track door knocks</li>
|
||||
<li>Mobile-friendly</li>
|
||||
<li>Works offline</li>
|
||||
</ul>
|
||||
<div class="service-tool">NocoDB + Map Viewer</div>
|
||||
</div>
|
||||
|
||||
<!-- Email System -->
|
||||
<div class="service-card grid-card">
|
||||
<div class="service-icon">📩</div>
|
||||
<h3>Email Blasts</h3>
|
||||
<div class="service-replaces">Replaces: Mailchimp (~$200+/mo)</div>
|
||||
<ul class="service-features">
|
||||
<li>Simple email creator</li>
|
||||
<li>Track opens/clicks</li>
|
||||
<li>Unlimited subscribers</li>
|
||||
<li>Pre-made templates</li>
|
||||
</ul>
|
||||
<div class="service-tool">Listmonk</div>
|
||||
</div>
|
||||
|
||||
<!-- Voter Database -->
|
||||
<div class="service-card grid-card">
|
||||
<div class="service-icon">🗃️</div>
|
||||
<h3>Voter Database</h3>
|
||||
<div class="service-replaces">Replaces: NationBuilder (~$500+/mo)</div>
|
||||
<ul class="service-features">
|
||||
<li>Spreadsheet interface</li>
|
||||
<li>Easy voter searches</li>
|
||||
<li>Custom survey forms</li>
|
||||
<li>Export walk lists</li>
|
||||
</ul>
|
||||
<div class="service-tool">NocoDB</div>
|
||||
</div>
|
||||
|
||||
<!-- Automation -->
|
||||
<div class="service-card grid-card">
|
||||
<div class="service-icon">🤖</div>
|
||||
<h3>Smart Automation</h3>
|
||||
<div class="service-replaces">Replaces: Zapier (~$300/month)</div>
|
||||
<ul class="service-features">
|
||||
<li>Auto follow-up emails</li>
|
||||
<li>Volunteer reminders</li>
|
||||
<li>Donation thank yous</li>
|
||||
<li>Pre-built workflows</li>
|
||||
</ul>
|
||||
<div class="service-tool">n8n</div>
|
||||
</div>
|
||||
|
||||
<!-- Website -->
|
||||
<div class="service-card grid-card">
|
||||
<div class="service-icon">🌐</div>
|
||||
<h3>Campaign Website</h3>
|
||||
<div class="service-replaces">Replaces: Squarespace (~$100/month)</div>
|
||||
<ul class="service-features">
|
||||
<li>Pre-designed templates</li>
|
||||
<li>Easy content updates</li>
|
||||
<li>Built-in blog</li>
|
||||
<li>Fast loading</li>
|
||||
</ul>
|
||||
<div class="service-tool">MkDocs</div>
|
||||
</div>
|
||||
|
||||
<!-- Volunteer Management -->
|
||||
<div class="service-card grid-card">
|
||||
<div class="service-icon">👥</div>
|
||||
<h3>Volunteer HQ</h3>
|
||||
<div class="service-replaces">Replaces: Mobilize (~$200/month)</div>
|
||||
<ul class="service-features">
|
||||
<li>Shift scheduling</li>
|
||||
<li>Automated reminders</li>
|
||||
<li>Skills matching</li>
|
||||
<li>Progress tracking</li>
|
||||
</ul>
|
||||
<div class="service-tool">NocoDB + n8n</div>
|
||||
</div>
|
||||
|
||||
<!-- Linking Features -->
|
||||
<div class="service-card grid-card">
|
||||
<div class="service-icon">👀</div>
|
||||
<h3> Mobile, Social, & Hot Link Friendly</h3>
|
||||
<div class="service-replaces">Replaces: Linktree (~$20/month)</div>
|
||||
<ul class="service-features">
|
||||
<li>Automatic Social Cards</li>
|
||||
<li>Hot Linking to All Assets & Headers</li>
|
||||
<li>Documentation Indexing & Search</li>
|
||||
<li>Progress tracking</li>
|
||||
</ul>
|
||||
<div class="service-tool">NocoDB + n8n</div>
|
||||
</div>
|
||||
|
||||
<!-- Rapid Response -->
|
||||
<div class="service-card grid-card">
|
||||
<div class="service-icon">⚡</div>
|
||||
<h3>Rapid Response</h3>
|
||||
<div class="service-replaces">Replaces: Slack + Google Docs (~$50/month)</div>
|
||||
<ul class="service-features">
|
||||
<li>Real-time collaboration</li>
|
||||
<li>Community Editing</li>
|
||||
<li>Open source documentation</li>
|
||||
<li>Version control</li>
|
||||
</ul>
|
||||
<div class="service-tool">Code Server + Gitea</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Subscribe Card -->
|
||||
<div class="subscribe-card grid-card">
|
||||
<h3>Subscribe</h3>
|
||||
<form action="https://newsletter.bnkops.com/subscription/form" method="POST" target="_blank" autocomplete="off">
|
||||
<input type="hidden" name="nonce">
|
||||
<p>
|
||||
<input type="email" name="email" required placeholder="E-mail">
|
||||
</p>
|
||||
<p>
|
||||
<input type="text" name="name" placeholder="Name (optional)">
|
||||
</p>
|
||||
<p>
|
||||
<input id="48b84" type="checkbox" name="l" checked value="48b841e7-841b-458b-95f8-63903a1c0912">
|
||||
<label for="48b84">~ Weekly Updates</label>
|
||||
</p>
|
||||
<input type="submit" value="Subscribe" class="action-btn primary">
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<!-- Comparison Grid -->
|
||||
<section class="comparison-grid">
|
||||
<div class="grid-container">
|
||||
<div class="section-header">
|
||||
<h2>Corporate SaaS vs Changemaker Lite</h2>
|
||||
<p>Why campaigns are switching to self-hosted FOSS</p>
|
||||
</div>
|
||||
|
||||
<div class="comparison-table">
|
||||
<div class="comparison-header grid-card">
|
||||
<div></div>
|
||||
<div class="compare-col">Corporate SaaS</div>
|
||||
<div class="compare-col highlight">Changemaker Lite</div>
|
||||
</div>
|
||||
|
||||
<div class="comparison-row grid-card">
|
||||
<div class="compare-label">Monthly Cost</div>
|
||||
<div class="compare-value bad">$1,200+</div>
|
||||
<div class="compare-value good">$0 Self-Hosted</div>
|
||||
</div>
|
||||
|
||||
<div class="comparison-row grid-card">
|
||||
<div class="compare-label">Data Ownership</div>
|
||||
<div class="compare-value bad">Their servers</div>
|
||||
<div class="compare-value good">100% yours</div>
|
||||
</div>
|
||||
|
||||
<div class="comparison-row grid-card">
|
||||
<div class="compare-label">Surveillance</div>
|
||||
<div class="compare-value bad">Always tracked</div>
|
||||
<div class="compare-value good">Zero tracking</div>
|
||||
</div>
|
||||
|
||||
<div class="comparison-row grid-card">
|
||||
<div class="compare-label">Vendor Lock-in</div>
|
||||
<div class="compare-value bad">Trapped forever</div>
|
||||
<div class="compare-value good">Export anytime</div>
|
||||
</div>
|
||||
|
||||
<div class="comparison-row grid-card">
|
||||
<div class="compare-label">Customization</div>
|
||||
<div class="compare-value bad">Limited options</div>
|
||||
<div class="compare-value good">Full control</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- Get Started Grid -->
|
||||
<section class="get-started-grid" id="get-started">
|
||||
<div class="grid-container">
|
||||
<div class="section-header">
|
||||
<h2>Start Your Digital Liberation</h2>
|
||||
<p>Three ways to break free from corporate campaign tech</p>
|
||||
</div>
|
||||
|
||||
<div class="options-grid">
|
||||
<!-- Plug & Play -->
|
||||
<div class="option-card grid-card featured">
|
||||
<div class="option-badge">Most Popular</div>
|
||||
<div class="option-icon">🚀</div>
|
||||
<h3>Plug & Play</h3>
|
||||
<div class="option-price">~$400 (Built to Suit)</div>
|
||||
<div class="option-features">
|
||||
<div class="feature">✓ Pre-configured hardware</div>
|
||||
<div class="feature">✓ 30-minute setup</div>
|
||||
<div class="feature">✓ All 11 services ready</div>
|
||||
<div class="feature">✓ Priority support</div>
|
||||
<div class="feature">✓ Ships on request</div>
|
||||
</div>
|
||||
<a href="https://bnkops.com/hardware" class="btn btn-primary">Order Hardware</a>
|
||||
</div>
|
||||
|
||||
<!-- DIY -->
|
||||
<div class="option-card grid-card">
|
||||
<div class="option-icon">🛠️</div>
|
||||
<h3>DIY Install</h3>
|
||||
<div class="option-price">Free</div>
|
||||
<div class="option-features">
|
||||
<div class="feature">✓ Use your hardware</div>
|
||||
<div class="feature">✓ Docker compose</div>
|
||||
<div class="feature">✓ Full documentation</div>
|
||||
<div class="feature">✓ Community support</div>
|
||||
<div class="feature">✓ Learn the system</div>
|
||||
</div>
|
||||
<a href="/getting-started" class="btn btn-secondary">Install Guide</a>
|
||||
</div>
|
||||
|
||||
<!-- Managed -->
|
||||
<div class="option-card grid-card">
|
||||
<div class="option-icon">☁️</div>
|
||||
<h3>Managed Hosting</h3>
|
||||
<div class="option-price">$???/mo</div>
|
||||
<div class="option-features">
|
||||
<div class="feature">✓ We manage everything</div>
|
||||
<div class="feature">✓ Your domain</div>
|
||||
<div class="feature">✓ Daily backups</div>
|
||||
<div class="feature">✓ 24/7 monitoring</div>
|
||||
<div class="feature">✓ Still your data</div>
|
||||
</div>
|
||||
<a href="https://bnkops.com" class="btn btn-secondary">Learn More</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cards in a row -->
|
||||
<div class="row-cards">
|
||||
<div class="final-cta grid-card">
|
||||
<h3>Join the Movement</h3>
|
||||
<p>Some of the biggest players in local politics are already using it; maybe give it a try?</p>
|
||||
<div class="cta-buttons">
|
||||
<a href="https://bnkops.com/newsletter" class="btn btn-primary">Get Campaign Tips</a>
|
||||
<a href="https://docs.bnkops.com" class="btn btn-secondary">Read Documentation</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="subscribe-card grid-card">
|
||||
<h3>Subscribe</h3>
|
||||
<form action="https://newsletter.bnkops.com/subscription/form" method="POST" target="_blank"
|
||||
autocomplete="off">
|
||||
<input type="hidden" name="nonce">
|
||||
<p>
|
||||
<input type="email" name="email" required placeholder="E-mail">
|
||||
</p>
|
||||
<p>
|
||||
<input type="text" name="name" placeholder="Name (optional)">
|
||||
</p>
|
||||
<p>
|
||||
<input id="48b84" type="checkbox" name="l" checked
|
||||
value="48b841e7-841b-458b-95f8-63903a1c0912">
|
||||
<label for="48b84">~ Weekly Updates</label>
|
||||
</p>
|
||||
<input type="submit" value="Subscribe" class="action-btn primary">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@@ -104,6 +104,8 @@ The tools we use shape the movements we build. Corporate tools create corporate
|
||||
|
||||
**No.** We specifically designed Changemaker Lite for organizers, activists, and movement builders who may not have technical backgrounds. Our philosophy is that everyone deserves digital sovereignty, not just people with computer science degrees.
|
||||
|
||||
This is not to say that you won't need to learn! These tools are just that; tools. They have no fancy or white-labeled marketing and are technical in nature. You will need to learn to use them, just as any worker needs to learn the power tools they use on the job.
|
||||
|
||||
### "What about convenience?"
|
||||
|
||||
Corporate platforms are convenient because they've extracted billions of dollars from users to fund that convenience. When you own your tools, there's a learning curve—but it's the same learning curve as learning to organize, learning to build power, learning to create change.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Code Server
|
||||
|
||||
Visual Studio Code in your browser for remote development.
|
||||
<div class="github-widget" data-repo="coder/code-server"></div>
|
||||
|
||||
## Overview
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ Changemaker Lite includes several powerful services that work together to provid
|
||||
|
||||
### [Code Server](code-server.md)
|
||||
**Port: 8888** | Visual Studio Code in your browser for remote development
|
||||
<div class="github-widget" data-repo="coder/code-server"></div>
|
||||
- Full IDE experience
|
||||
- Extensions support
|
||||
- Git integration
|
||||
@@ -13,6 +14,7 @@ Changemaker Lite includes several powerful services that work together to provid
|
||||
|
||||
### [Listmonk](listmonk.md)
|
||||
**Port: 9000** | Self-hosted newsletter and mailing list manager
|
||||
<div class="github-widget" data-repo="knadh/listmonk"></div>
|
||||
- Email campaigns
|
||||
- Subscriber management
|
||||
- Analytics
|
||||
@@ -27,6 +29,7 @@ Changemaker Lite includes several powerful services that work together to provid
|
||||
|
||||
### [MkDocs Material](mkdocs.md)
|
||||
**Port: 4000** | Documentation site generator with live preview
|
||||
<div class="github-widget" data-repo="squidfunk/mkdocs-material"></div>
|
||||
- Material Design theme
|
||||
- Live reload
|
||||
- Search functionality
|
||||
@@ -41,6 +44,7 @@ Changemaker Lite includes several powerful services that work together to provid
|
||||
|
||||
### [n8n](n8n.md)
|
||||
**Port: 5678** | Workflow automation tool
|
||||
<div class="github-widget" data-repo="n8n-io/n8n"></div>
|
||||
- Visual workflow editor
|
||||
- 400+ integrations
|
||||
- Custom code execution
|
||||
@@ -48,6 +52,7 @@ Changemaker Lite includes several powerful services that work together to provid
|
||||
|
||||
### [NocoDB](nocodb.md)
|
||||
**Port: 8090** | No-code database platform
|
||||
<div class="github-widget" data-repo="nocodb/nocodb"></div>
|
||||
- Smart spreadsheet interface
|
||||
- Form builder and API generation
|
||||
- Real-time collaboration
|
||||
@@ -55,6 +60,7 @@ Changemaker Lite includes several powerful services that work together to provid
|
||||
|
||||
### [Homepage](homepage.md)
|
||||
**Port: 3010** | Modern dashboard for all services
|
||||
<div class="github-widget" data-repo="gethomepage/homepage"></div>
|
||||
- Service dashboard and monitoring
|
||||
- Docker integration
|
||||
- Customizable layout
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# n8n
|
||||
|
||||
<div class="github-widget" data-repo="n8n-io/n8n"></div>
|
||||
|
||||
Workflow automation tool for connecting services and automating tasks.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -0,0 +1,554 @@
|
||||
.login-button {
|
||||
display: inline-block;
|
||||
padding: 2px 10px;
|
||||
margin-left: auto; /* Push the button to the right */
|
||||
margin-right: 10px;
|
||||
background-color: hsl(315, 80%, 42%); /* Use a solid, high-contrast color */
|
||||
color: #fff; /* Ensure text is white */
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
transition: background-color 0.2s ease;
|
||||
font-size: 0.9em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.login-button:hover {
|
||||
background-color: #003c8f; /* Darker shade for hover */
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.git-code-button {
|
||||
display: inline-block;
|
||||
background: #30363d;
|
||||
color: white !important;
|
||||
padding: 0.6rem 1.2rem;
|
||||
border-radius: 20px;
|
||||
text-decoration: none;
|
||||
font-size: 0.95rem;
|
||||
font-weight: bold;
|
||||
margin-left: 1rem;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.git-code-button:hover {
|
||||
background: #444d56;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.git-code-button .material-icons {
|
||||
font-size: 1rem;
|
||||
vertical-align: middle;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* GitHub Widget Styles */
|
||||
.github-widget {
|
||||
margin: 1.5rem 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.github-widget-container {
|
||||
border: 1px solid rgba(var(--md-primary-fg-color--rgb), 0.15);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, var(--md-code-bg-color) 0%, rgba(var(--md-primary-fg-color--rgb), 0.03) 100%);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.github-widget-container::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, var(--md-primary-fg-color), var(--md-accent-fg-color));
|
||||
border-radius: 12px 12px 0 0;
|
||||
}
|
||||
|
||||
.github-widget-container:hover {
|
||||
border-color: var(--md-accent-fg-color);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.github-widget-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.github-widget-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.github-icon {
|
||||
color: var(--md-default-fg-color--light);
|
||||
flex-shrink: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.github-widget .repo-link {
|
||||
color: var(--md-accent-fg-color);
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
transition: color 0.2s ease;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.github-widget .repo-link:hover {
|
||||
color: var(--md-primary-fg-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.github-widget-stats {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--md-default-fg-color);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
background: rgba(var(--md-primary-fg-color--rgb), 0.08);
|
||||
padding: 4px 8px;
|
||||
border-radius: 16px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.stat-item:hover {
|
||||
background: rgba(var(--md-accent-fg-color--rgb), 0.15);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.stat-item svg {
|
||||
color: var(--md-accent-fg-color);
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.github-widget-description {
|
||||
color: var(--md-default-fg-color--light);
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
font-style: italic;
|
||||
padding: 12px;
|
||||
background: rgba(var(--md-default-fg-color--rgb), 0.03);
|
||||
border-radius: 8px;
|
||||
border-left: 3px solid var(--md-accent-fg-color);
|
||||
}
|
||||
|
||||
.github-widget-footer {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
color: var(--md-default-fg-color--lighter);
|
||||
border-top: 1px solid rgba(var(--md-default-fg-color--rgb), 0.1);
|
||||
padding-top: 16px;
|
||||
margin-top: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.language-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.language-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.last-update,
|
||||
.license-info {
|
||||
color: var(--md-default-fg-color--lighter);
|
||||
}
|
||||
|
||||
/* Loading State */
|
||||
.github-widget-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 20px;
|
||||
color: var(--md-default-fg-color--light);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid var(--md-default-fg-color--lightest);
|
||||
border-top: 2px solid var(--md-accent-fg-color);
|
||||
border-radius: 50%;
|
||||
animation: github-widget-spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes github-widget-spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Error State */
|
||||
.github-widget-error {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 20px;
|
||||
color: var(--md-typeset-color);
|
||||
text-align: center;
|
||||
background: var(--md-code-bg-color);
|
||||
border: 1px solid #f85149;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.github-widget-error svg {
|
||||
color: #f85149;
|
||||
}
|
||||
|
||||
.github-widget-error small {
|
||||
color: var(--md-default-fg-color--lighter);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* Dark mode specific adjustments */
|
||||
[data-md-color-scheme="slate"] .github-widget-container {
|
||||
background: var(--md-code-bg-color);
|
||||
border-color: #30363d;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] .github-widget-container:hover {
|
||||
border-color: var(--md-accent-fg-color);
|
||||
}
|
||||
|
||||
/* Multiple widgets in a row */
|
||||
.github-widgets-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.github-widgets-row .github-widget {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Compact widget variant */
|
||||
.github-widget.compact .github-widget-container {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.github-widget.compact .github-widget-header {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.github-widget.compact .github-widget-description {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.github-widget.compact .github-widget-footer {
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
/* GitHub Widget Responsive - placed after existing mobile styles */
|
||||
@media (max-width: 768px) {
|
||||
.github-widget-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.github-widget-stats {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.github-widget-footer {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Gitea Widget Styles */
|
||||
.gitea-widget {
|
||||
margin: 1.5rem 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.gitea-widget-container {
|
||||
border: 1px solid rgba(var(--md-primary-fg-color--rgb), 0.15);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, var(--md-code-bg-color) 0%, rgba(var(--md-primary-fg-color--rgb), 0.03) 100%);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.gitea-widget-container::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, #609926, #89c442);
|
||||
border-radius: 12px 12px 0 0;
|
||||
}
|
||||
|
||||
.gitea-widget-container:hover {
|
||||
border-color: #89c442;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.gitea-widget-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.gitea-widget-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.gitea-icon {
|
||||
color: #89c442;
|
||||
flex-shrink: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.gitea-widget .repo-link {
|
||||
color: #89c442;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
transition: color 0.2s ease;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.gitea-widget .repo-link:hover {
|
||||
color: #609926;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.gitea-widget-stats {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.gitea-widget .stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--md-default-fg-color);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
background: rgba(137, 196, 66, 0.1);
|
||||
padding: 4px 8px;
|
||||
border-radius: 16px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.gitea-widget .stat-item:hover {
|
||||
background: rgba(137, 196, 66, 0.2);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.gitea-widget .stat-item svg {
|
||||
color: #89c442;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.gitea-widget-description {
|
||||
color: var(--md-default-fg-color--light);
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
font-style: italic;
|
||||
padding: 12px;
|
||||
background: rgba(var(--md-default-fg-color--rgb), 0.03);
|
||||
border-radius: 8px;
|
||||
border-left: 3px solid #89c442;
|
||||
}
|
||||
|
||||
.gitea-widget-footer {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
color: var(--md-default-fg-color--lighter);
|
||||
border-top: 1px solid rgba(var(--md-default-fg-color--rgb), 0.1);
|
||||
padding-top: 16px;
|
||||
margin-top: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.gitea-widget .language-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.gitea-widget .language-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.gitea-widget .last-update,
|
||||
.gitea-widget .license-info {
|
||||
color: var(--md-default-fg-color--lighter);
|
||||
}
|
||||
|
||||
/* Gitea Loading State */
|
||||
.gitea-widget-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 20px;
|
||||
color: var(--md-default-fg-color--light);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.gitea-widget-loading .loading-spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid var(--md-default-fg-color--lightest);
|
||||
border-top: 2px solid #89c442;
|
||||
border-radius: 50%;
|
||||
animation: gitea-widget-spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes gitea-widget-spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Gitea Error State */
|
||||
.gitea-widget-error {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 20px;
|
||||
color: var(--md-typeset-color);
|
||||
text-align: center;
|
||||
background: var(--md-code-bg-color);
|
||||
border: 1px solid #f85149;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.gitea-widget-error svg {
|
||||
color: #f85149;
|
||||
}
|
||||
|
||||
.gitea-widget-error small {
|
||||
color: var(--md-default-fg-color--lighter);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* Dark mode specific adjustments for Gitea */
|
||||
[data-md-color-scheme="slate"] .gitea-widget-container {
|
||||
background: var(--md-code-bg-color);
|
||||
border-color: #30363d;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] .gitea-widget-container:hover {
|
||||
border-color: #89c442;
|
||||
}
|
||||
|
||||
/* Multiple Gitea widgets in a row */
|
||||
.gitea-widgets-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.gitea-widgets-row .gitea-widget {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Compact Gitea widget variant */
|
||||
.gitea-widget.compact .gitea-widget-container {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.gitea-widget.compact .gitea-widget-header {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.gitea-widget.compact .gitea-widget-description {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.gitea-widget.compact .gitea-widget-footer {
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
/* Gitea Widget Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.gitea-widget-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.gitea-widget-stats {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.gitea-widget-footer {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,7 +265,7 @@ body[data-md-template="home"] .md-main__inner {
|
||||
.stat-number {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
color: var(--trans-blue);
|
||||
color: var(--trans-pink);
|
||||
margin: 0;
|
||||
animation: number-glow 4s ease-in-out infinite;
|
||||
}
|
||||
@@ -309,6 +309,8 @@ body[data-md-template="home"] .md-main__inner {
|
||||
.section-header {
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-lg);
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
@@ -672,6 +674,21 @@ body[data-md-template="home"] .md-main__inner {
|
||||
}
|
||||
}
|
||||
|
||||
/* Row cards at bottom: two cards in a row on desktop, stack on mobile */
|
||||
.row-cards {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--space-sm);
|
||||
margin-top: var(--space-lg);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.row-cards {
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
}
|
||||
|
||||
/* =================================
|
||||
PERFORMANCE
|
||||
================================= */
|
||||
|
||||
Reference in New Issue
Block a user