Tonne of updates to campaigns and such

This commit is contained in:
admin 2026-01-17 10:39:26 -07:00
parent e641360738
commit 097c101b38
64 changed files with 11698 additions and 2111 deletions

165
CLAUDE.md Normal file
View File

@ -0,0 +1,165 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
**Free Alberta** is a political campaign and advocacy platform built on the Changemaker Lite framework. It's a self-hosted, Docker-based system for political organizing and constituent engagement in Alberta, featuring campaign tools, interactive maps, and food resource directories.
## Architecture
### Service Structure
This is a Docker Compose monorepo with 28+ services. The four main applications are:
| Service | Port | Stack | Purpose |
|---------|------|-------|---------|
| **influence/** | 3333 | Express.js + NocoDB | Campaign & constituent engagement tool - representative lookup, email advocacy, response walls |
| **map/** | 3000 | Express.js + Leaflet.js | Geographic organizing with walk sheets, QR codes, routing |
| **freealberta-food/** | 3003 | Express.js + PostgreSQL | Food resource directory with scraping from InformAlberta, 211, and PDF sources |
| **freealberta-lander/** | 3020 | Nginx static | Landing page |
### Infrastructure Services
- **NocoDB** (8090): No-code database used by influence and map apps
- **Redis** (6379): Shared caching and sessions
- **PostgreSQL**: Separate instances for Listmonk and Food Resources
- **Listmonk** (9001): Email campaign management
- **n8n** (5678): Workflow automation
- **Code Server** (8888): Browser-based VS Code
- **Gitea** (3030): Self-hosted git
- **MkDocs** (4000 dev / 4001 built): Documentation
- **MailHog** (SMTP 1025, UI 8025): Development email testing
### Monitoring Stack (profile: monitoring)
Prometheus (9090), Grafana (3001), cAdvisor, Node Exporter, Alertmanager, Gotify (8889)
## Common Commands
### Root Level (Docker Infrastructure)
```bash
# Start all core services
docker compose up -d
# Start with monitoring stack
docker compose --profile monitoring up -d
# Initial configuration wizard (creates .env)
./config.sh
# Fix container directory permissions (EACCES errors)
./fix-permissions.sh
# Production deployment with Cloudflare tunnel
./start-production.sh
# View logs for a specific service
docker compose logs -f <service-name>
```
### Influence App (influence/app/)
```bash
npm start # Production
npm run dev # Development with nodemon
```
### Map App (map/app/)
```bash
npm start # Production
npm run dev # Development with nodemon
```
### Food Resources App (freealberta-food/app/)
```bash
npm start # Production
npm run dev # Development with nodemon
npm run db:init # Initialize database schema
npm run scrape # Run all scrapers
npm run scrape:informalberta # Scrape InformAlberta only
npm run scrape:211 # Scrape 211 Alberta only
npm run scrape:pdf # Parse Edmonton's Food Bank PDF
```
### MkDocs Documentation
```bash
# From mkdocs/ directory
mkdocs serve # Development server with live reload
mkdocs build # Build static site
```
## Key Architectural Patterns
### Data Flow
- **Influence & Map apps** use NocoDB as their backend database via REST API
- **Food Resources app** uses direct PostgreSQL connection
- **Representative data** in Influence comes from Represent OpenNorth Canada API with NocoDB caching fallback
### Mapping Stack (100% FOSS)
All mapping uses open-source alternatives to Google Maps:
- **Tiles**: OpenStreetMap
- **Frontend**: Leaflet.js
- **Geocoding**: Nominatim + Photon (with fallback chain)
- **Routing**: OSRM (Open Source Routing Machine)
### Common Backend Patterns
All Express apps use:
- **Helmet.js** for security headers
- **express-rate-limit** for API protection
- **Winston** for logging with daily rotation
- **Compression** middleware
- **CORS** enabled
### Environment Configuration
- Root `.env` file contains all service credentials (generated by `config.sh`)
- Each app has its own `.env` for app-specific settings
- Email testing routes through MailHog in development (SMTP port 1025)
## Service Interconnections
```
┌─────────────┐ ┌─────────────┐
│ Influence │────▶│ NocoDB │◀────│ Map │
│ :3333 │ │ :8090 │ │ :3000 │
└─────────────┘ └─────────────┘ └────────────┘
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Redis │ │ PostgreSQL │◀────│ Food │
│ :6379 │ │ (shared) │ │ :3003 │
└─────────────┘ └─────────────┘ └────────────┘
```
## File Structure Conventions
Each Express app follows this structure:
```
app/
├── controllers/ # Route handlers
├── routes/ # API endpoint definitions
├── middleware/ # Auth, rate limiting, CSRF
├── services/ # External API integrations
├── public/ # Frontend assets (HTML, CSS, JS)
├── utils/ # Logger, helpers
├── server.js # Entry point
└── package.json
```
## External API Dependencies
- **Represent OpenNorth** (represent.opennorth.ca): Canadian elected representative data
- **Nominatim/Photon**: OpenStreetMap geocoding
- **OSRM**: Open source routing
## Cloudflare Tunnel
Production uses Cloudflare tunnel for HTTPS. Configuration at `configs/cloudflare/tunnel-config.yml`.

View File

@ -6,10 +6,10 @@
<title>Admin - Free Alberta Food</title> <title>Admin - Free Alberta Food</title>
<meta name="robots" content="noindex, nofollow"> <meta name="robots" content="noindex, nofollow">
<!-- Google Fonts --> <!-- Google Fonts - Roboto to match MkDocs Material theme -->
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700;900&display=swap" rel="stylesheet">
<!-- Custom CSS --> <!-- Custom CSS -->
<link rel="stylesheet" href="/css/styles.css"> <link rel="stylesheet" href="/css/styles.css">
@ -218,6 +218,24 @@
</div> </div>
</div> </div>
<!-- Theme Toggle Button -->
<button id="themeToggle" class="theme-toggle" title="Toggle dark mode">
<svg class="sun-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
<svg class="moon-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</button>
<!-- Footer --> <!-- Footer -->
<footer class="footer"> <footer class="footer">
<div class="footer-content"> <div class="footer-content">
@ -225,6 +243,33 @@
</div> </div>
</footer> </footer>
<!-- Theme Toggle Script - Dark mode is default -->
<script>
// Initialize theme on page load
(function() {
const savedTheme = localStorage.getItem('theme');
const prefersLight = window.matchMedia('(prefers-color-scheme: light)').matches;
// Only set light mode if explicitly saved or system prefers light
if (savedTheme === 'light' || (!savedTheme && prefersLight)) {
document.documentElement.setAttribute('data-theme', 'light');
}
// Dark is default, no attribute needed
})();
// Theme toggle handler
document.getElementById('themeToggle').addEventListener('click', function() {
const currentTheme = document.documentElement.getAttribute('data-theme');
const isLight = currentTheme === 'light';
if (isLight) {
document.documentElement.removeAttribute('data-theme');
localStorage.setItem('theme', 'dark');
} else {
document.documentElement.setAttribute('data-theme', 'light');
localStorage.setItem('theme', 'light');
}
});
</script>
<!-- Custom JS --> <!-- Custom JS -->
<script src="/js/admin.js"></script> <script src="/js/admin.js"></script>
</body> </body>

File diff suppressed because it is too large Load Diff

View File

@ -9,10 +9,10 @@
<!-- Leaflet CSS for maps --> <!-- Leaflet CSS for maps -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<!-- Google Fonts --> <!-- Google Fonts - Roboto to match MkDocs Material theme -->
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700;900&display=swap" rel="stylesheet">
<!-- Custom CSS --> <!-- Custom CSS -->
<link rel="stylesheet" href="/css/styles.css"> <link rel="stylesheet" href="/css/styles.css">
@ -459,6 +459,7 @@
<h1>Directions</h1> <h1>Directions</h1>
<p class="print-date" id="printDate"></p> <p class="print-date" id="printDate"></p>
</div> </div>
<div id="printMapContainer" class="print-map-container"></div>
<div id="printDirections"></div> <div id="printDirections"></div>
<div class="print-footer"> <div class="print-footer">
<div class="print-footer-brand"> <div class="print-footer-brand">
@ -469,6 +470,24 @@
</div> </div>
</div> </div>
<!-- Theme Toggle Button -->
<button id="themeToggle" class="theme-toggle" title="Toggle dark mode">
<svg class="sun-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
<svg class="moon-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</button>
<!-- Footer --> <!-- Footer -->
<footer class="footer"> <footer class="footer">
<div class="footer-content"> <div class="footer-content">
@ -481,6 +500,9 @@
<!-- Leaflet JS --> <!-- Leaflet JS -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<!-- html2canvas for map capture in print -->
<script src="https://unpkg.com/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
<!-- Custom JS --> <!-- Custom JS -->
<script src="/js/app.js"></script> <script src="/js/app.js"></script>
</body> </body>

View File

@ -28,6 +28,7 @@ class FoodResourceApp {
} }
async init() { async init() {
this.initTheme();
this.bindEvents(); this.bindEvents();
this.initMap(); this.initMap();
this.setupInfiniteScroll(); this.setupInfiniteScroll();
@ -38,7 +39,50 @@ class FoodResourceApp {
]); ]);
} }
// Theme toggle functionality - dark mode is default
initTheme() {
// Check for saved theme preference or use system preference
const savedTheme = localStorage.getItem('theme');
const prefersLight = window.matchMedia('(prefers-color-scheme: light)').matches;
// Only set light mode if explicitly saved or system prefers light
if (savedTheme === 'light' || (!savedTheme && prefersLight)) {
document.documentElement.setAttribute('data-theme', 'light');
}
// Dark is default, no attribute needed
// Listen for system theme changes
window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
if (e.matches) {
document.documentElement.setAttribute('data-theme', 'light');
} else {
document.documentElement.removeAttribute('data-theme');
}
}
});
}
toggleTheme() {
const currentTheme = document.documentElement.getAttribute('data-theme');
const isLight = currentTheme === 'light';
if (isLight) {
document.documentElement.removeAttribute('data-theme');
localStorage.setItem('theme', 'dark');
} else {
document.documentElement.setAttribute('data-theme', 'light');
localStorage.setItem('theme', 'light');
}
}
bindEvents() { bindEvents() {
// Theme toggle
const themeToggle = document.getElementById('themeToggle');
if (themeToggle) {
themeToggle.addEventListener('click', () => this.toggleTheme());
}
// Search // Search
document.getElementById('searchBtn').addEventListener('click', () => this.handleSearch()); document.getElementById('searchBtn').addEventListener('click', () => this.handleSearch());
document.getElementById('searchInput').addEventListener('keypress', (e) => { document.getElementById('searchInput').addEventListener('keypress', (e) => {
@ -490,7 +534,10 @@ class FoodResourceApp {
} }
const btn = document.getElementById('closestBtn'); const btn = document.getElementById('closestBtn');
const originalContent = btn.innerHTML;
btn.disabled = true; btn.disabled = true;
btn.classList.add('loading');
btn.innerHTML = '<span class="btn-spinner"></span> Finding...';
navigator.geolocation.getCurrentPosition( navigator.geolocation.getCurrentPosition(
async (position) => { async (position) => {
@ -571,8 +618,14 @@ class FoodResourceApp {
} }
btn.disabled = false; btn.disabled = false;
btn.classList.remove('loading');
btn.innerHTML = originalContent;
}, },
(error) => { (error) => {
btn.disabled = false;
btn.classList.remove('loading');
btn.innerHTML = originalContent;
let message = 'Unable to get your location.'; let message = 'Unable to get your location.';
if (error.message.includes('secure origins') || error.code === 1) { if (error.message.includes('secure origins') || error.code === 1) {
message = 'Location access requires HTTPS or permission. Please enable location access.'; message = 'Location access requires HTTPS or permission. Please enable location access.';
@ -580,7 +633,6 @@ class FoodResourceApp {
message += ' ' + error.message; message += ' ' + error.message;
} }
alert(message); alert(message);
btn.disabled = false;
} }
); );
} }
@ -1045,17 +1097,52 @@ class FoodResourceApp {
} }
} }
printDirections() { async printDirections() {
if (!this.currentRoute || !this.currentDestination) return; if (!this.currentRoute || !this.currentDestination) return;
const printBtn = document.getElementById('printDirectionsBtn');
const originalBtnContent = printBtn.innerHTML;
// Show loading state
printBtn.disabled = true;
printBtn.innerHTML = `
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="btn-spinner-icon">
<circle cx="12" cy="12" r="10" stroke-dasharray="32" stroke-dashoffset="32">
<animate attributeName="stroke-dashoffset" values="32;0" dur="1s" repeatCount="indefinite"/>
</circle>
</svg>
Preparing...
`;
const printContent = document.getElementById('printDirections'); const printContent = document.getElementById('printDirections');
const printDate = document.getElementById('printDate'); const printDate = document.getElementById('printDate');
const printMapContainer = document.getElementById('printMapContainer');
const route = this.currentRoute; const route = this.currentRoute;
const dest = this.currentDestination; const dest = this.currentDestination;
const mode = document.getElementById('travelMode').value; const mode = document.getElementById('travelMode').value;
const modeText = mode === 'driving' ? 'Drive' : mode === 'walking' ? 'Walk' : 'Cycle'; const modeText = mode === 'driving' ? 'Drive' : mode === 'walking' ? 'Walk' : 'Cycle';
const modeIcon = mode === 'driving' ? 'By car' : mode === 'walking' ? 'On foot' : 'By bicycle'; const modeIcon = mode === 'driving' ? 'By car' : mode === 'walking' ? 'On foot' : 'By bicycle';
// Capture the directions map as an image
try {
const mapElement = document.getElementById('directionsMap');
if (mapElement && typeof html2canvas !== 'undefined') {
const canvas = await html2canvas(mapElement, {
useCORS: true,
allowTaint: true,
logging: false,
scale: 2 // Higher quality for print
});
const mapImageUrl = canvas.toDataURL('image/png');
printMapContainer.innerHTML = `<img src="${mapImageUrl}" alt="Route Map">`;
} else {
printMapContainer.innerHTML = '';
}
} catch (error) {
console.error('Failed to capture map:', error);
printMapContainer.innerHTML = '<p style="text-align:center;color:#64748b;padding:1rem;">Map image could not be captured</p>';
}
// Set the print date // Set the print date
const now = new Date(); const now = new Date();
printDate.textContent = `Generated on ${now.toLocaleDateString('en-US', { printDate.textContent = `Generated on ${now.toLocaleDateString('en-US', {
@ -1125,6 +1212,13 @@ class FoodResourceApp {
</div> </div>
`; `;
// Restore button state
printBtn.disabled = false;
printBtn.innerHTML = originalBtnContent;
// Small delay to ensure content is rendered before printing
await new Promise(resolve => setTimeout(resolve, 100));
window.print(); window.print();
} }

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" data-theme="light">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
@ -40,11 +40,11 @@
} }
.nav-btn:hover { .nav-btn:hover {
background: #3498db; background: #2196f3;
} }
.nav-btn.active { .nav-btn.active {
background: #3498db; background: #2196f3;
} }
.tab-content { .tab-content {
@ -223,7 +223,7 @@
} }
.btn-primary { .btn-primary {
background: #3498db; background: #2196f3;
color: white; color: white;
} }
@ -255,7 +255,7 @@
.spinner { .spinner {
border: 4px solid #f3f3f3; border: 4px solid #f3f3f3;
border-radius: 50%; border-radius: 50%;
border-top: 4px solid #3498db; border-top: 4px solid #2196f3;
width: 40px; width: 40px;
height: 40px; height: 40px;
animation: spin 1s linear infinite; animation: spin 1s linear infinite;
@ -306,7 +306,7 @@
.analytics-stat h3 { .analytics-stat h3 {
font-size: 2rem; font-size: 2rem;
margin: 0 0 0.5rem 0; margin: 0 0 0.5rem 0;
color: #3498db; color: #2196f3;
} }
.modal-overlay { .modal-overlay {
@ -373,7 +373,7 @@
.form-group textarea:focus, .form-group textarea:focus,
.form-group select:focus { .form-group select:focus {
outline: none; outline: none;
border-color: #3498db; border-color: #2196f3;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1); box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
background: white; background: white;
} }
@ -385,7 +385,7 @@
.status-select select { .status-select select {
appearance: none; appearance: none;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #2196f3 0%, #1976d2 100%);
color: white; color: white;
font-weight: 600; font-weight: 600;
padding: 1rem 3rem 1rem 1rem; padding: 1rem 3rem 1rem 1rem;
@ -405,7 +405,7 @@
.status-select select option:hover, .status-select select option:hover,
.status-select select option:checked { .status-select select option:checked {
background: #3498db; background: #2196f3;
color: white; color: white;
} }
@ -444,7 +444,7 @@
} }
.checkbox-item:hover { .checkbox-item:hover {
border-color: #3498db; border-color: #2196f3;
transform: translateY(-1px); transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(52, 152, 219, 0.15); box-shadow: 0 2px 8px rgba(52, 152, 219, 0.15);
} }
@ -453,7 +453,7 @@
width: auto; width: auto;
margin-right: 0.75rem; margin-right: 0.75rem;
transform: scale(1.2); transform: scale(1.2);
accent-color: #3498db; accent-color: #2196f3;
} }
.checkbox-item label { .checkbox-item label {
@ -466,7 +466,7 @@
.checkbox-item:has(input:checked) { .checkbox-item:has(input:checked) {
background: linear-gradient(135deg, #e3f2fd 0%, #f3e5f5 100%); background: linear-gradient(135deg, #e3f2fd 0%, #f3e5f5 100%);
border-color: #3498db; border-color: #2196f3;
} }
.checkbox-item:has(input:checked) label { .checkbox-item:has(input:checked) label {
@ -509,7 +509,7 @@
} }
.btn-primary { .btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #2196f3 0%, #1976d2 100%);
color: white; color: white;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
} }
@ -581,7 +581,7 @@
} }
.user-badge.user { .user-badge.user {
background: #3498db; background: #2196f3;
color: white; color: white;
} }
@ -641,7 +641,7 @@
.search-dropdown input:focus { .search-dropdown input:focus {
outline: none; outline: none;
border-color: #3498db; border-color: #2196f3;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1); box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
} }
@ -809,7 +809,7 @@
} }
.recipient-info .recipient-email { .recipient-info .recipient-email {
color: #3498db; color: #2196f3;
font-size: 0.9rem; font-size: 0.9rem;
margin-bottom: 0.25rem; margin-bottom: 0.25rem;
} }
@ -842,8 +842,8 @@
} }
.btn-icon.edit:hover { .btn-icon.edit:hover {
border-color: #3498db; border-color: #2196f3;
color: #3498db; color: #2196f3;
} }
.btn-icon.delete:hover { .btn-icon.delete:hover {
@ -895,6 +895,447 @@
justify-content: center; justify-content: center;
} }
} }
/* Dark mode overrides for admin panel */
[data-theme="dark"] .admin-container {
background-color: var(--page-bg-color, #121212);
}
[data-theme="dark"] .admin-nav {
background: #252525;
}
[data-theme="dark"] .campaign-card {
background: var(--card-bg-color, #1e1e1e);
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .campaign-card-body h3 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .form-group label {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .form-group input,
[data-theme="dark"] .form-group select,
[data-theme="dark"] .form-group textarea {
background-color: #252525;
color: var(--text-color, rgba(255, 255, 255, 0.87));
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .section-header h2 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .users-table {
background: var(--card-bg-color, #1e1e1e);
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .users-table th {
background: #252525;
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .users-table td {
color: var(--text-color, rgba(255, 255, 255, 0.87));
border-bottom-color: var(--card-border-color, #333);
}
[data-theme="dark"] .users-table tr:hover {
background: #252525;
}
[data-theme="dark"] .form-card {
background: var(--card-bg-color, #1e1e1e);
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .form-card h3 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .checkbox-item label {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .responses-list {
background: var(--card-bg-color, #1e1e1e);
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .response-item {
border-bottom-color: var(--card-border-color, #333);
}
[data-theme="dark"] .response-item:hover {
background: #252525;
}
[data-theme="dark"] #sync-status-display {
background: var(--card-bg-color, #1e1e1e) !important;
}
[data-theme="dark"] #sync-status-display > div > div {
background: #252525 !important;
border-color: var(--card-border-color, #333) !important;
}
[data-theme="dark"] #sync-status-display strong {
color: var(--text-color, rgba(255, 255, 255, 0.87)) !important;
}
/* ==========================================
Comprehensive Dark Mode for Admin Panel
========================================== */
/* Admin utility classes */
.admin-card {
background: white;
}
.admin-text-muted {
color: #666;
}
[data-theme="dark"] .admin-card {
background: var(--card-bg-color, #1e1e1e);
}
[data-theme="dark"] .admin-text-muted {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
/* Checkbox group container */
[data-theme="dark"] .checkbox-group {
background: #252525;
border-color: var(--card-border-color, #333);
}
/* Checkbox items - the cards inside checkbox group */
[data-theme="dark"] .checkbox-item {
background: var(--card-bg-color, #1e1e1e);
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .checkbox-item:hover {
border-color: var(--md-primary-fg-color, #2196f3);
box-shadow: 0 2px 8px rgba(33, 150, 243, 0.2);
}
[data-theme="dark"] .checkbox-item label {
color: var(--text-color, rgba(255, 255, 255, 0.87)) !important;
}
[data-theme="dark"] .checkbox-item:has(input:checked) {
background: linear-gradient(135deg, rgba(33, 150, 243, 0.15) 0%, rgba(100, 181, 246, 0.15) 100%);
border-color: var(--md-primary-fg-color, #2196f3);
}
[data-theme="dark"] .checkbox-item:has(input:checked) label {
color: var(--md-primary-fg-color-light, #42a5f5) !important;
}
/* Form grid container */
[data-theme="dark"] .form-grid {
background: var(--card-bg-color, #1e1e1e);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
/* Campaign selector dropdown */
[data-theme="dark"] .campaign-selector {
background: #252525;
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .campaign-selector label {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
/* Search dropdown */
[data-theme="dark"] .search-dropdown input {
background: var(--card-bg-color, #1e1e1e);
color: var(--text-color, rgba(255, 255, 255, 0.87));
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .search-dropdown input::placeholder {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
[data-theme="dark"] .dropdown-menu {
background: var(--card-bg-color, #1e1e1e);
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .dropdown-item {
color: var(--text-color, rgba(255, 255, 255, 0.87));
border-bottom-color: var(--card-border-color, #333);
}
[data-theme="dark"] .dropdown-item:hover {
background: #252525;
}
[data-theme="dark"] .dropdown-item.selected {
background: rgba(33, 150, 243, 0.2);
color: var(--md-primary-fg-color-light, #42a5f5);
}
/* Section headers */
[data-theme="dark"] .section-header {
color: var(--text-color, rgba(255, 255, 255, 0.87));
border-bottom-color: var(--card-border-color, #333);
}
/* Tab content headings */
[data-theme="dark"] .tab-content h2 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
/* Campaign header inside cards */
[data-theme="dark"] .campaign-header h3 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
/* Campaign meta text */
[data-theme="dark"] .campaign-meta {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
/* User cards */
[data-theme="dark"] .user-card {
background: var(--card-bg-color, #1e1e1e);
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .user-info h4 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .user-info p {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
/* Response cards */
[data-theme="dark"] .response-card {
background: var(--card-bg-color, #1e1e1e);
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .response-meta {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
[data-theme="dark"] .response-content {
background: #252525;
}
[data-theme="dark"] .response-content h4 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .response-content p {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
/* Recipient cards */
[data-theme="dark"] .recipient-card {
background: var(--card-bg-color, #1e1e1e);
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .recipient-info h5 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .recipient-info .recipient-meta {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
/* Add recipient form */
[data-theme="dark"] #add-recipient-form {
background: #252525;
}
[data-theme="dark"] #add-recipient-form h4 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
/* Recipients list header */
[data-theme="dark"] #recipients-list-container h4 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
/* Custom recipients section description */
[data-theme="dark"] #edit-custom-recipients-section p {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6)) !important;
}
/* Modal content */
[data-theme="dark"] .modal-content {
background: var(--card-bg-color, #1e1e1e);
}
[data-theme="dark"] .modal-header {
border-bottom-color: var(--card-border-color, #333);
}
[data-theme="dark"] .modal-header h3 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
/* Bulk import help box */
[data-theme="dark"] .bulk-import-help {
background: rgba(33, 150, 243, 0.15);
}
[data-theme="dark"] .bulk-import-help h4 {
color: var(--md-primary-fg-color-light, #42a5f5);
}
[data-theme="dark"] .bulk-import-help code {
background: #252525;
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
/* Listmonk/Email Sync tab */
[data-theme="dark"] #listmonk-tab > div[style*="background: white"],
[data-theme="dark"] #listmonk-tab > div[style*="background-color: white"] {
background: var(--card-bg-color, #1e1e1e) !important;
}
[data-theme="dark"] #listmonk-stats-section {
background: var(--card-bg-color, #1e1e1e) !important;
}
[data-theme="dark"] #listmonk-stats-section p {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
/* Sync buttons container */
[data-theme="dark"] .sync-buttons + div[style*="background"] {
background: var(--card-bg-color, #1e1e1e) !important;
}
/* Analytics stat boxes */
[data-theme="dark"] .analytics-stat {
background: #252525;
}
[data-theme="dark"] .analytics-stat h3 {
color: var(--md-primary-fg-color, #2196f3);
}
/* Empty state */
[data-theme="dark"] .empty-state {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
/* Loading text */
[data-theme="dark"] .loading p {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
/* Email body formatting tips box */
[data-theme="dark"] div[style*="background: #e3f2fd"] {
background: rgba(33, 150, 243, 0.15) !important;
}
[data-theme="dark"] div[style*="background: #e3f2fd"] strong {
color: var(--md-primary-fg-color-light, #42a5f5) !important;
}
[data-theme="dark"] div[style*="background: #e3f2fd"] li,
[data-theme="dark"] div[style*="background: #e3f2fd"] code {
color: var(--text-color, rgba(255, 255, 255, 0.87)) !important;
}
/* Cover photo hint text */
[data-theme="dark"] small[style*="color: #666"] {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6)) !important;
}
/* Status select dropdown */
[data-theme="dark"] .status-select select option {
background: var(--card-bg-color, #1e1e1e);
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
/* Button icon borders */
[data-theme="dark"] .btn-icon {
border-color: var(--card-border-color, #333);
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
[data-theme="dark"] .btn-icon:hover {
background: #252525;
}
/* Message success/error boxes */
[data-theme="dark"] .message-success {
background: rgba(39, 174, 96, 0.15);
border-color: rgba(39, 174, 96, 0.3);
color: #4ade80;
}
[data-theme="dark"] .message-error {
background: rgba(231, 76, 60, 0.15);
border-color: rgba(231, 76, 60, 0.3);
color: #f87171;
}
/* Filter selects in Response Moderation */
[data-theme="dark"] #admin-campaign-filter,
[data-theme="dark"] #admin-response-status {
background: var(--card-bg-color, #1e1e1e);
color: var(--text-color, rgba(255, 255, 255, 0.87));
border-color: var(--card-border-color, #333);
}
/* Inline styles overrides using important */
[data-theme="dark"] [style*="color: #666"],
[data-theme="dark"] [style*="color:#666"] {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6)) !important;
}
[data-theme="dark"] [style*="color: #2c3e50"],
[data-theme="dark"] [style*="color:#2c3e50"] {
color: var(--text-color, rgba(255, 255, 255, 0.87)) !important;
}
[data-theme="dark"] [style*="color: #444"],
[data-theme="dark"] [style*="color:#444"] {
color: var(--text-color, rgba(255, 255, 255, 0.87)) !important;
}
/* Form row labels */
[data-theme="dark"] .form-row h2 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
/* Sync progress section */
[data-theme="dark"] #sync-progress > div[style*="background: #f8f9fa"] {
background: #252525 !important;
}
[data-theme="dark"] #sync-results {
background: rgba(33, 150, 243, 0.15) !important;
border-left-color: var(--md-primary-fg-color, #2196f3) !important;
}
/* Advanced options white box */
[data-theme="dark"] #listmonk-tab div[style*="background: white; padding: 1.5rem"] {
background: var(--card-bg-color, #1e1e1e) !important;
}
/* Note text in listmonk section */
[data-theme="dark"] #listmonk-tab p[style*="color: #666"] {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6)) !important;
}
</style> </style>
</head> </head>
<body> <body>
@ -992,6 +1433,20 @@ I am writing as your constituent to express my concern about...
Sincerely, Sincerely,
[Your Name]"></textarea> [Your Name]"></textarea>
<div style="background: #e3f2fd; padding: 1rem; border-radius: 8px; margin-top: 0.75rem; font-size: 0.9rem;">
<strong style="color: #1565c0;">Dynamic Variables:</strong>
<ul style="margin: 0.5rem 0 0 1.5rem; padding: 0; color: #444;">
<li><code>[Representative Name]</code> - Replaced with the recipient's actual name</li>
<li><code>[Your Name]</code> - Replaced with the sender's name</li>
<li><code>[Your Postal Code]</code> - Replaced with the sender's postal code</li>
</ul>
<strong style="color: #1565c0; display: block; margin-top: 0.75rem;">Formatting Tips:</strong>
<ul style="margin: 0.5rem 0 0 1.5rem; padding: 0; color: #444;">
<li>Start a line with <code>-</code> or <code>*</code> for bullet points</li>
<li>Start a line with <code>1.</code> or <code>2.</code> for numbered lists</li>
<li>Use blank lines to separate paragraphs</li>
</ul>
</div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -1130,6 +1585,20 @@ Sincerely,
<div class="form-group"> <div class="form-group">
<label for="edit-email-body">Email Body *</label> <label for="edit-email-body">Email Body *</label>
<textarea id="edit-email-body" name="email_body" rows="8" required></textarea> <textarea id="edit-email-body" name="email_body" rows="8" required></textarea>
<div style="background: #e3f2fd; padding: 1rem; border-radius: 8px; margin-top: 0.75rem; font-size: 0.9rem;">
<strong style="color: #1565c0;">Dynamic Variables:</strong>
<ul style="margin: 0.5rem 0 0 1.5rem; padding: 0; color: #444;">
<li><code>[Representative Name]</code> - Replaced with the recipient's actual name</li>
<li><code>[Your Name]</code> - Replaced with the sender's name</li>
<li><code>[Your Postal Code]</code> - Replaced with the sender's postal code</li>
</ul>
<strong style="color: #1565c0; display: block; margin-top: 0.75rem;">Formatting Tips:</strong>
<ul style="margin: 0.5rem 0 0 1.5rem; padding: 0; color: #444;">
<li>Start a line with <code>-</code> or <code>*</code> for bullet points</li>
<li>Start a line with <code>1.</code> or <code>2.</code> for numbered lists</li>
<li>Use blank lines to separate paragraphs</li>
</ul>
</div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -1347,8 +1816,8 @@ Sincerely,
</div> </div>
<div class="section-header">🚀 Sync Actions</div> <div class="section-header">🚀 Sync Actions</div>
<div style="background: white; padding: 1.5rem; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.05); margin-bottom: 2rem;"> <div class="admin-card" style="padding: 1.5rem; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.05); margin-bottom: 2rem;">
<p style="color: #666; margin-bottom: 1.5rem;">Sync campaign participants and custom recipients to Listmonk email lists for targeted email campaigns.</p> <p class="admin-text-muted" style="margin-bottom: 1.5rem;">Sync campaign participants and custom recipients to Listmonk email lists for targeted email campaigns.</p>
<div class="sync-buttons" style="display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.5rem;"> <div class="sync-buttons" style="display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.5rem;">
<button class="btn btn-primary" id="sync-participants-btn" onclick="syncToListmonk('participants')"> <button class="btn btn-primary" id="sync-participants-btn" onclick="syncToListmonk('participants')">
@ -1364,7 +1833,7 @@ Sincerely,
<div id="sync-progress" style="display: none; margin-top: 1.5rem;"> <div id="sync-progress" style="display: none; margin-top: 1.5rem;">
<div style="background: #f8f9fa; border-radius: 8px; overflow: hidden; height: 30px; margin-bottom: 1rem;"> <div style="background: #f8f9fa; border-radius: 8px; overflow: hidden; height: 30px; margin-bottom: 1rem;">
<div id="sync-progress-bar" style="background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); height: 100%; width: 0%; transition: width 0.3s; display: flex; align-items: center; justify-content: center; color: white; font-weight: 600; font-size: 0.85rem;"> <div id="sync-progress-bar" style="background: linear-gradient(90deg, #2196f3 0%, #1976d2 100%); height: 100%; width: 0%; transition: width 0.3s; display: flex; align-items: center; justify-content: center; color: white; font-weight: 600; font-size: 0.85rem;">
</div> </div>
</div> </div>
<div id="sync-results" style="background: #e3f2fd; padding: 1rem; border-radius: 8px; border-left: 4px solid #2196f3;"> <div id="sync-results" style="background: #e3f2fd; padding: 1rem; border-radius: 8px; border-left: 4px solid #2196f3;">
@ -1374,7 +1843,7 @@ Sincerely,
</div> </div>
<div class="section-header">⚙️ Advanced Options</div> <div class="section-header">⚙️ Advanced Options</div>
<div style="background: white; padding: 1.5rem; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.05); margin-bottom: 2rem;"> <div class="admin-card" style="padding: 1.5rem; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.05); margin-bottom: 2rem;">
<div style="display: flex; gap: 1rem; flex-wrap: wrap;"> <div style="display: flex; gap: 1rem; flex-wrap: wrap;">
<button class="btn btn-secondary" id="test-connection-btn" onclick="testListmonkConnection()"> <button class="btn btn-secondary" id="test-connection-btn" onclick="testListmonkConnection()">
🔌 Test Connection 🔌 Test Connection
@ -1383,15 +1852,15 @@ Sincerely,
⚠️ Reinitialize Lists ⚠️ Reinitialize Lists
</button> </button>
</div> </div>
<p style="color: #666; margin-top: 1rem; font-size: 0.9rem;"> <p class="admin-text-muted" style="margin-top: 1rem; font-size: 0.9rem;">
<strong>Note:</strong> Reinitializing lists will recreate all email list structures. Use only if lists are corrupted or missing. <strong>Note:</strong> Reinitializing lists will recreate all email list structures. Use only if lists are corrupted or missing.
</p> </p>
</div> </div>
<div class="section-header">📈 Email List Statistics</div> <div class="section-header">📈 Email List Statistics</div>
<div id="listmonk-stats-section" style="background: white; padding: 1.5rem; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.05);"> <div id="listmonk-stats-section" class="admin-card" style="padding: 1.5rem; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.05);">
<div class="stats-list"> <div class="stats-list">
<p style="color: #666; text-align: center;">Loading statistics...</p> <p class="admin-text-muted" style="text-align: center;">Loading statistics...</p>
</div> </div>
</div> </div>
</div> </div>
@ -1514,6 +1983,43 @@ Jane Doe,jane@example.com,Director,Example Inc,</code>
</div> </div>
</div> </div>
<!-- Theme Toggle Button -->
<button class="theme-toggle" id="theme-toggle-btn" aria-label="Toggle dark mode">
<svg class="sun-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z"/>
</svg>
<svg class="moon-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z"/>
</svg>
</button>
<!-- Theme Toggle Script -->
<script>
// Load saved theme preference immediately
(function() {
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
document.documentElement.setAttribute('data-theme', savedTheme);
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.setAttribute('data-theme', 'dark');
}
})();
// Add event listener for theme toggle button
document.addEventListener('DOMContentLoaded', function() {
const themeToggleBtn = document.getElementById('theme-toggle-btn');
if (themeToggleBtn) {
themeToggleBtn.addEventListener('click', function() {
const html = document.documentElement;
const currentTheme = html.getAttribute('data-theme');
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
html.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
});
}
});
</script>
<script src="js/api-client.js"></script> <script src="js/api-client.js"></script>
<script src="js/auth.js"></script> <script src="js/auth.js"></script>
<script src="js/custom-recipients.js"></script> <script src="js/custom-recipients.js"></script>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" data-theme="light">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
@ -7,7 +7,7 @@
<link rel="stylesheet" href="/css/styles.css"> <link rel="stylesheet" href="/css/styles.css">
<style> <style>
.campaign-header { .campaign-header {
background: linear-gradient(135deg, #3498db, #2c3e50); background: linear-gradient(135deg, #2196f3, #1976d2);
color: white; color: white;
padding: 3rem 0; padding: 3rem 0;
text-align: center; text-align: center;
@ -394,7 +394,7 @@
} }
.step.active { .step.active {
color: #3498db; color: #2196f3;
font-weight: bold; font-weight: bold;
} }
@ -433,7 +433,7 @@
.response-wall-button { .response-wall-button {
display: inline-block; display: inline-block;
padding: 1rem 2rem; padding: 1rem 2rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #2196f3 0%, #1976d2 100%);
color: white; color: white;
text-decoration: none; text-decoration: none;
border-radius: 50px; border-radius: 50px;
@ -513,17 +513,209 @@
.campaign-header h1 { .campaign-header h1 {
font-size: 2rem; font-size: 2rem;
} }
.progress-steps { .progress-steps {
flex-direction: column; flex-direction: column;
gap: 0.5rem; gap: 0.5rem;
} }
.step:not(:last-child)::after { .step:not(:last-child)::after {
content: '↓'; content: '↓';
position: static; position: static;
} }
} }
/* Dark mode overrides for campaign page */
[data-theme="dark"] body {
background-color: var(--page-bg-color, #121212);
}
[data-theme="dark"] .container {
background-color: var(--page-bg-color, #121212);
}
[data-theme="dark"] .campaign-container h2 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .call-to-action {
background: rgba(255, 243, 205, 0.1);
border-color: rgba(255, 234, 167, 0.3);
}
[data-theme="dark"] .call-to-action p,
[data-theme="dark"] .call-to-action strong,
[data-theme="dark"] .call-to-action h2,
[data-theme="dark"] .call-to-action h3 {
color: rgba(255, 255, 255, 0.87);
}
[data-theme="dark"] #success-section h2 {
color: #27ae60;
}
[data-theme="dark"] #success-section p {
color: rgba(255, 255, 255, 0.87);
}
/* Main form card - dark blue */
[data-theme="dark"] .user-info-form {
background: rgba(10, 60, 100, 0.85);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
[data-theme="dark"] .user-info-form .form-group label {
color: rgba(255, 255, 255, 0.9);
}
[data-theme="dark"] .user-info-form h2 {
color: rgba(255, 255, 255, 0.95);
}
[data-theme="dark"] .user-info-form p,
[data-theme="dark"] .user-info-form strong {
color: rgba(255, 255, 255, 0.87);
}
[data-theme="dark"] .user-info-form .form-group input {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.3);
color: white;
}
[data-theme="dark"] .user-info-form .form-group input::placeholder {
color: rgba(255, 255, 255, 0.5);
}
/* Email preview - dark blue */
[data-theme="dark"] .email-preview {
background: rgba(10, 60, 100, 0.85);
border-color: rgba(33, 150, 243, 0.4);
}
[data-theme="dark"] .email-preview h3 {
color: rgba(255, 255, 255, 0.9);
}
[data-theme="dark"] .email-preview p,
[data-theme="dark"] .email-subject,
[data-theme="dark"] .email-body {
color: rgba(255, 255, 255, 0.87);
}
/* Rep cards - dark blue */
[data-theme="dark"] .rep-card {
background: rgba(10, 60, 100, 0.85);
border-color: rgba(33, 150, 243, 0.4);
}
[data-theme="dark"] .rep-card.custom-recipient {
background: linear-gradient(135deg, rgba(10, 60, 100, 0.85) 0%, rgba(20, 80, 120, 0.85) 100%);
border-left-color: #9b59b6;
}
[data-theme="dark"] .rep-name,
[data-theme="dark"] .rep-details h4 {
color: rgba(255, 255, 255, 0.95);
}
[data-theme="dark"] .rep-title,
[data-theme="dark"] .rep-party,
[data-theme="dark"] .rep-details p {
color: rgba(255, 255, 255, 0.7);
}
[data-theme="dark"] #representatives-section h2 {
color: rgba(255, 255, 255, 0.95);
}
[data-theme="dark"] #representatives-section > p,
[data-theme="dark"] #representatives-section strong {
color: rgba(255, 255, 255, 0.87);
}
[data-theme="dark"] .method-option label {
color: rgba(255, 255, 255, 0.87);
}
[data-theme="dark"] .rep-photo {
background: rgba(255, 255, 255, 0.1);
}
/* Share dropdown */
[data-theme="dark"] .share-dropdown {
background: rgba(10, 60, 100, 0.95);
border-color: rgba(33, 150, 243, 0.4);
}
[data-theme="dark"] .share-dropdown-item {
color: rgba(255, 255, 255, 0.9);
background: rgba(255, 255, 255, 0.1);
}
[data-theme="dark"] .share-dropdown-item:hover {
background: rgba(255, 255, 255, 0.2);
}
[data-theme="dark"] .share-dropdown-item svg {
fill: rgba(255, 255, 255, 0.9);
}
/* Loading content */
[data-theme="dark"] .loading-content {
background: rgba(10, 60, 100, 0.95);
}
[data-theme="dark"] .custom-recipient-card {
background: linear-gradient(135deg, rgba(10, 60, 100, 0.85) 0%, rgba(20, 80, 120, 0.85) 100%);
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .email-method-link {
color: var(--md-primary-fg-color--light, #42a5f5);
}
[data-theme="dark"] .response-wall-container {
background: rgba(10, 60, 100, 0.85);
}
[data-theme="dark"] .response-wall-container h3 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .response-wall-container p {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
[data-theme="dark"] footer {
border-top-color: var(--card-border-color, #333);
}
[data-theme="dark"] footer p,
[data-theme="dark"] footer a {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
[data-theme="dark"] .loading-overlay {
background: rgba(0, 0, 0, 0.9);
}
[data-theme="dark"] .loading-content p {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
/* Step indicators / Progress bar */
[data-theme="dark"] .progress-steps {
background: rgba(10, 60, 100, 0.85);
}
[data-theme="dark"] .step {
color: rgba(255, 255, 255, 0.7);
}
[data-theme="dark"] .step.active {
color: var(--md-primary-fg-color, #2196f3);
}
</style> </style>
</head> </head>
<body> <body>
@ -794,6 +986,43 @@
</div> </div>
</footer> </footer>
<!-- Theme Toggle Button -->
<button class="theme-toggle" id="theme-toggle-btn" aria-label="Toggle dark mode">
<svg class="sun-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z"/>
</svg>
<svg class="moon-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z"/>
</svg>
</button>
<!-- Theme Toggle Script -->
<script>
// Load saved theme preference immediately
(function() {
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
document.documentElement.setAttribute('data-theme', savedTheme);
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.setAttribute('data-theme', 'dark');
}
})();
// Add event listener for theme toggle button
document.addEventListener('DOMContentLoaded', function() {
const themeToggleBtn = document.getElementById('theme-toggle-btn');
if (themeToggleBtn) {
themeToggleBtn.addEventListener('click', function() {
const html = document.documentElement;
const currentTheme = html.getAttribute('data-theme');
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
html.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
});
}
});
</script>
<script src="/js/api-client.js"></script> <script src="/js/api-client.js"></script>
<script src="/js/campaign.js"></script> <script src="/js/campaign.js"></script>
<script> <script>

View File

@ -2,7 +2,7 @@
/* Campaign Header Styles */ /* Campaign Header Styles */
.response-wall-header { .response-wall-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #2196f3 0%, #1976d2 100%);
color: white; color: white;
padding: 3rem 0; padding: 3rem 0;
text-align: center; text-align: center;
@ -313,7 +313,7 @@
.stats-banner { .stats-banner {
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #2196f3 0%, #1976d2 100%);
color: white; color: white;
padding: 2rem; padding: 2rem;
border-radius: 12px; border-radius: 12px;
@ -367,9 +367,11 @@
.filter-group select { .filter-group select {
padding: 0.5rem; padding: 0.5rem;
border: 1px solid #ddd; border: 1px solid var(--card-border-color, #ddd);
border-radius: 4px; border-radius: 4px;
font-size: 1rem; font-size: 1rem;
background-color: var(--card-bg-color, white);
color: var(--text-color, inherit);
} }
#submit-response-btn { #submit-response-btn {
@ -378,12 +380,12 @@
/* Response Card */ /* Response Card */
.response-card { .response-card {
background: white; background: var(--card-bg-color, white);
border: 1px solid #e1e8ed; border: 1px solid var(--card-border-color, #e1e8ed);
border-radius: 8px; border-radius: 8px;
padding: 1.5rem; padding: 1.5rem;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
transition: box-shadow 0.3s ease; transition: box-shadow 0.3s ease, background-color 0.3s ease;
} }
.response-card:hover { .response-card:hover {
@ -403,12 +405,12 @@
.response-rep-info h3 { .response-rep-info h3 {
margin: 0 0 0.25rem 0; margin: 0 0 0.25rem 0;
color: #1a202c; color: var(--text-color, #1a202c);
font-size: 1.2rem; font-size: 1.2rem;
} }
.response-rep-info .rep-meta { .response-rep-info .rep-meta {
color: #7f8c8d; color: var(--text-muted-color, #7f8c8d);
font-size: 0.9rem; font-size: 0.9rem;
} }
@ -434,7 +436,7 @@
} }
.badge-level { .badge-level {
background: #3498db; background: #2196f3;
color: white; color: white;
} }
@ -448,13 +450,14 @@
} }
.response-text { .response-text {
background: #f8f9fa; background: var(--md-code-bg-color, #f8f9fa);
padding: 1rem; padding: 1rem;
border-left: 4px solid #3498db; border-left: 4px solid var(--md-primary-fg-color, #2196f3);
border-radius: 4px; border-radius: 4px;
margin-bottom: 1rem; margin-bottom: 1rem;
white-space: pre-wrap; white-space: pre-wrap;
line-height: 1.6; line-height: 1.6;
color: var(--text-color, inherit);
} }
.user-comment { .user-comment {
@ -479,7 +482,7 @@
.response-screenshot img { .response-screenshot img {
max-width: 100%; max-width: 100%;
border-radius: 4px; border-radius: 4px;
border: 1px solid #ddd; border: 1px solid var(--card-border-color, #ddd);
cursor: pointer; cursor: pointer;
transition: opacity 0.3s ease; transition: opacity 0.3s ease;
} }
@ -493,11 +496,11 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding-top: 1rem; padding-top: 1rem;
border-top: 1px solid #e1e8ed; border-top: 1px solid var(--card-border-color, #e1e8ed);
} }
.response-meta { .response-meta {
color: #7f8c8d; color: var(--text-muted-color, #7f8c8d);
font-size: 0.9rem; font-size: 0.9rem;
} }
@ -511,9 +514,9 @@
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
border: 2px solid #3498db; border: 2px solid var(--md-primary-fg-color, #2196f3);
background: white; background: var(--card-bg-color, white);
color: #3498db; color: var(--md-primary-fg-color, #2196f3);
border-radius: 20px; border-radius: 20px;
cursor: pointer; cursor: pointer;
transition: all 0.3s ease; transition: all 0.3s ease;
@ -521,13 +524,13 @@
} }
.upvote-btn:hover { .upvote-btn:hover {
background: #3498db; background: var(--md-primary-fg-color, #2196f3);
color: white; color: white;
transform: translateY(-2px); transform: translateY(-2px);
} }
.upvote-btn.upvoted { .upvote-btn.upvoted {
background: #3498db; background: var(--md-primary-fg-color, #2196f3);
color: white; color: white;
} }
@ -546,7 +549,7 @@
gap: 0.5rem; gap: 0.5rem;
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
border: 2px solid #27ae60; border: 2px solid #27ae60;
background: white; background: var(--card-bg-color, white);
color: #27ae60; color: #27ae60;
border-radius: 20px; border-radius: 20px;
cursor: pointer; cursor: pointer;
@ -575,7 +578,7 @@
.empty-state { .empty-state {
text-align: center; text-align: center;
padding: 3rem; padding: 3rem;
color: #7f8c8d; color: var(--text-muted-color, #7f8c8d);
} }
.empty-state p { .empty-state p {
@ -597,7 +600,7 @@
} }
.modal-content { .modal-content {
background-color: white; background-color: var(--card-bg-color, white);
margin: 5% auto; margin: 5% auto;
padding: 2rem; padding: 2rem;
border-radius: 8px; border-radius: 8px;
@ -606,7 +609,7 @@
} }
.close { .close {
color: #aaa; color: var(--text-muted-color, #aaa);
float: right; float: right;
font-size: 28px; font-size: 28px;
font-weight: bold; font-weight: bold;
@ -615,7 +618,7 @@
.close:hover, .close:hover,
.close:focus { .close:focus {
color: #000; color: var(--text-color, #000);
} }
.form-group { .form-group {
@ -626,7 +629,7 @@
display: block; display: block;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
font-weight: 600; font-weight: 600;
color: #1a202c; color: var(--text-color, #1a202c);
} }
.form-group input, .form-group input,
@ -634,15 +637,17 @@
.form-group textarea { .form-group textarea {
width: 100%; width: 100%;
padding: 0.75rem; padding: 0.75rem;
border: 1px solid #ddd; border: 1px solid var(--card-border-color, #ddd);
border-radius: 4px; border-radius: 4px;
font-size: 1rem; font-size: 1rem;
background-color: var(--card-bg-color, white);
color: var(--text-color, inherit);
} }
.form-group small { .form-group small {
display: block; display: block;
margin-top: 0.25rem; margin-top: 0.25rem;
color: #7f8c8d; color: var(--text-muted-color, #7f8c8d);
} }
/* Postal Lookup Styles */ /* Postal Lookup Styles */
@ -663,10 +668,11 @@
#rep-select { #rep-select {
width: 100%; width: 100%;
padding: 0.5rem; padding: 0.5rem;
border: 2px solid #3498db; border: 2px solid var(--md-primary-fg-color, #2196f3);
border-radius: 4px; border-radius: 4px;
font-size: 0.95rem; font-size: 0.95rem;
background: white; background: var(--card-bg-color, white);
color: var(--text-color, inherit);
cursor: pointer; cursor: pointer;
} }
@ -680,10 +686,10 @@
} }
#rep-select-group { #rep-select-group {
background: #f8f9fa; background: var(--md-code-bg-color, #f8f9fa);
padding: 1rem; padding: 1rem;
border-radius: 4px; border-radius: 4px;
border: 1px solid #e1e8ed; border: 1px solid var(--card-border-color, #e1e8ed);
} }
.form-actions { .form-actions {
@ -718,7 +724,7 @@
.loading { .loading {
text-align: center; text-align: center;
padding: 2rem; padding: 2rem;
color: #7f8c8d; color: var(--text-muted-color, #7f8c8d);
} }
.load-more-container { .load-more-container {
@ -882,3 +888,128 @@
opacity: 1; opacity: 1;
} }
} }
/* ==========================================
Dark Mode Overrides for Response Wall
========================================== */
[data-theme="dark"] .response-card {
background: var(--card-bg-color, #1e1e1e);
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .response-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
[data-theme="dark"] .response-rep-info h3 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .response-rep-info .rep-meta {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
[data-theme="dark"] .response-text {
background: #252525;
border-left-color: var(--md-primary-fg-color, #2196f3);
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .user-comment {
background: rgba(255, 243, 205, 0.1);
border-left-color: #ffc107;
}
[data-theme="dark"] .user-comment-label {
color: #ffc107;
}
[data-theme="dark"] .response-footer {
border-top-color: var(--card-border-color, #333);
}
[data-theme="dark"] .response-meta {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
[data-theme="dark"] .upvote-btn {
background: var(--card-bg-color, #1e1e1e);
border-color: var(--md-primary-fg-color, #2196f3);
color: var(--md-primary-fg-color, #2196f3);
}
[data-theme="dark"] .upvote-btn:hover,
[data-theme="dark"] .upvote-btn.upvoted {
background: var(--md-primary-fg-color, #2196f3);
color: white;
}
[data-theme="dark"] .verify-btn {
background: var(--card-bg-color, #1e1e1e);
}
[data-theme="dark"] .filter-group select {
background-color: var(--card-bg-color, #1e1e1e);
color: var(--text-color, rgba(255, 255, 255, 0.87));
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .modal-content {
background-color: var(--card-bg-color, #1e1e1e);
}
[data-theme="dark"] .form-group label {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .form-group input,
[data-theme="dark"] .form-group select,
[data-theme="dark"] .form-group textarea {
background-color: #252525;
color: var(--text-color, rgba(255, 255, 255, 0.87));
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .form-group small {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
[data-theme="dark"] #rep-select {
background: #252525;
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] #rep-select-group {
background: #252525;
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .qrcode-modal-content {
background-color: var(--card-bg-color, #1e1e1e);
}
[data-theme="dark"] .qrcode-modal-content h2 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .qrcode-container {
background: #252525;
}
[data-theme="dark"] .qrcode-instructions {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
[data-theme="dark"] .empty-state {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
[data-theme="dark"] .loading {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
[data-theme="dark"] .share-socials-menu {
background: var(--card-bg-color, #1e1e1e);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}

View File

@ -1,3 +1,46 @@
/* CSS Variables - Matching Free Alberta Landing Page Theme */
:root {
/* Light theme */
--md-primary-fg-color: #2196f3;
--md-primary-fg-color--light: #42a5f5;
--md-primary-fg-color--dark: #1976d2;
--md-primary-bg-color: #ffffff;
--md-accent-fg-color: #526cfe;
--md-accent-bg-color: #ffffff;
--md-typeset-color: rgba(0, 0, 0, 0.87);
--md-code-bg-color: #f5f5f5;
/* App-specific colors */
--page-bg-color: #f5f5f5;
--card-bg-color: #ffffff;
--card-border-color: #ddd;
--text-color: #333;
--text-muted-color: #666;
--shadow-color: rgba(0, 0, 0, 0.15);
--shadow-color-heavy: rgba(0, 0, 0, 0.25);
}
[data-theme="dark"] {
/* Dark theme */
--md-primary-fg-color: #2196f3;
--md-primary-fg-color--light: #42a5f5;
--md-primary-fg-color--dark: #1976d2;
--md-primary-bg-color: #1e1e1e;
--md-accent-fg-color: #526cfe;
--md-accent-bg-color: #1e1e1e;
--md-typeset-color: rgba(255, 255, 255, 0.87);
--md-code-bg-color: #2d2d2d;
/* App-specific colors */
--page-bg-color: #121212;
--card-bg-color: #1e1e1e;
--card-border-color: #333;
--text-color: rgba(255, 255, 255, 0.87);
--text-muted-color: rgba(255, 255, 255, 0.6);
--shadow-color: rgba(0, 0, 0, 0.25);
--shadow-color-heavy: rgba(0, 0, 0, 0.4);
}
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -7,8 +50,9 @@
body { body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6; line-height: 1.6;
color: #333; color: var(--text-color);
background-color: #f5f5f5; background-color: var(--page-bg-color);
transition: background-color 0.3s ease, color 0.3s ease;
} }
.container { .container {
@ -21,10 +65,10 @@ header {
text-align: center; text-align: center;
margin-bottom: 40px; margin-bottom: 40px;
padding: 60px 20px; padding: 60px 20px;
background: linear-gradient(135deg, #005a9c 0%, #007acc 50%, #0099ff 100%); background: linear-gradient(135deg, #2196f3 0%, #42a5f5 50%, #64b5f6 100%);
background-size: 200% 200%; background-size: 200% 200%;
border-radius: 12px; border-radius: 12px;
box-shadow: 0 8px 24px rgba(0, 90, 156, 0.2); box-shadow: 0 8px 24px rgba(33, 150, 243, 0.2);
position: relative; position: relative;
overflow: hidden; overflow: hidden;
animation: gradientShift 15s ease infinite; animation: gradientShift 15s ease infinite;
@ -161,13 +205,13 @@ header {
/* Unified Header Section */ /* Unified Header Section */
.unified-header-section { .unified-header-section {
position: relative; position: relative;
background: linear-gradient(135deg, #005a9c 0%, #007acc 50%, #0099ff 100%); background: linear-gradient(135deg, #2196f3 0%, #42a5f5 50%, #64b5f6 100%);
background-size: 200% 200%; background-size: 200% 200%;
animation: gradientShift 15s ease infinite; animation: gradientShift 15s ease infinite;
border-radius: 12px; border-radius: 12px;
padding: 60px 20px 40px; padding: 60px 20px 40px;
margin-bottom: 40px; margin-bottom: 40px;
box-shadow: 0 8px 24px rgba(0, 90, 156, 0.2); box-shadow: 0 8px 24px rgba(33, 150, 243, 0.2);
overflow: hidden; overflow: hidden;
text-align: center; text-align: center;
transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1); transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1);
@ -505,25 +549,25 @@ header p {
.btn-primary { .btn-primary {
color: white; color: white;
background-color: #005a9c; background-color: #2196f3;
border-color: #005a9c; border-color: #2196f3;
} }
.btn-primary:hover { .btn-primary:hover {
background-color: #004a7c; background-color: #1976d2;
border-color: #004a7c; border-color: #1976d2;
} }
.btn-secondary { .btn-secondary {
color: #005a9c; color: #2196f3;
background-color: white; background-color: white;
border-color: #005a9c; border-color: #2196f3;
} }
.btn-secondary:hover { .btn-secondary:hover {
color: white; color: white;
background-color: #005a9c; background-color: #2196f3;
border-color: #005a9c; border-color: #2196f3;
} }
.btn-success { .btn-success {
@ -558,17 +602,19 @@ header p {
.form-group textarea { .form-group textarea {
width: 100%; width: 100%;
padding: 12px; padding: 12px;
border: 2px solid #ddd; border: 2px solid var(--card-border-color);
border-radius: 4px; border-radius: 4px;
font-size: 16px; font-size: 16px;
transition: border-color 0.3s ease; transition: border-color 0.3s ease, background-color 0.3s ease;
background-color: var(--card-bg-color);
color: var(--text-color);
} }
.form-group input:focus, .form-group input:focus,
.form-group textarea:focus { .form-group textarea:focus {
outline: none; outline: none;
border-color: #005a9c; border-color: #2196f3;
box-shadow: 0 0 0 2px rgba(0, 90, 156, 0.1); box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1);
} }
.input-group { .input-group {
@ -582,12 +628,13 @@ header p {
} }
#postal-form { #postal-form {
background: white; background: var(--card-bg-color);
padding: 30px; padding: 30px;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px var(--shadow-color);
max-width: 600px; max-width: 600px;
margin: 0 auto; margin: 0 auto;
transition: background-color 0.3s ease;
} }
/* Buttons */ /* Buttons */
@ -604,12 +651,12 @@ header p {
} }
.btn-primary { .btn-primary {
background-color: #005a9c; background-color: #2196f3;
color: white; color: white;
} }
.btn-primary:hover { .btn-primary:hover {
background-color: #004a7c; background-color: #1976d2;
transform: translateY(-1px); transform: translateY(-1px);
} }
@ -635,8 +682,8 @@ header p {
} }
.spinner { .spinner {
border: 4px solid #f3f3f3; border: 4px solid var(--card-border-color);
border-top: 4px solid #005a9c; border-top: 4px solid var(--md-primary-fg-color);
border-radius: 50%; border-radius: 50%;
width: 40px; width: 40px;
height: 40px; height: 40px;
@ -675,35 +722,37 @@ header p {
} }
.location-info { .location-info {
background: white; background: var(--card-bg-color);
padding: 20px; padding: 20px;
border-radius: 8px; border-radius: 8px;
margin-bottom: 30px; margin-bottom: 30px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px var(--shadow-color);
transition: background-color 0.3s ease;
} }
.location-info h3 { .location-info h3 {
color: #005a9c; color: var(--md-primary-fg-color);
margin-bottom: 10px; margin-bottom: 10px;
} }
.rep-category { .rep-category {
margin-bottom: 40px; margin-bottom: 40px;
background: white; background: var(--card-bg-color);
border-radius: 8px; border-radius: 8px;
padding: 20px; padding: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px var(--shadow-color);
/* Prevent width changes when inline composer is added */ /* Prevent width changes when inline composer is added */
position: relative; position: relative;
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
transition: background-color 0.3s ease;
} }
.rep-category h3 { .rep-category h3 {
color: #005a9c; color: var(--md-primary-fg-color);
margin: 0 0 20px 0; margin: 0 0 20px 0;
padding-bottom: 10px; padding-bottom: 10px;
border-bottom: 2px solid #e9ecef; border-bottom: 2px solid var(--card-border-color);
} }
.rep-cards { .rep-cards {
@ -713,11 +762,11 @@ header p {
} }
.rep-card { .rep-card {
border: 1px solid #ddd; border: 1px solid var(--card-border-color);
border-radius: 8px; border-radius: 8px;
padding: 20px; padding: 20px;
background: #fafafa; background: var(--md-code-bg-color);
transition: transform 0.2s, box-shadow 0.2s; transition: transform 0.2s, box-shadow 0.2s, background-color 0.3s ease;
display: flex; display: flex;
gap: 20px; gap: 20px;
align-items: flex-start; align-items: flex-start;
@ -725,7 +774,7 @@ header p {
.rep-card:hover { .rep-card:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 8px var(--shadow-color-heavy);
} }
.rep-photo { .rep-photo {
@ -740,21 +789,21 @@ header p {
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
border-radius: 50%; border-radius: 50%;
border: 2px solid #005a9c; border: 2px solid #2196f3;
} }
.rep-photo-fallback { .rep-photo-fallback {
width: 100%; width: 100%;
height: 100%; height: 100%;
border-radius: 50%; border-radius: 50%;
background: linear-gradient(135deg, #005a9c, #007acc); background: linear-gradient(135deg, #2196f3, #42a5f5);
color: white; color: white;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-weight: bold; font-weight: bold;
font-size: 1.2em; font-size: 1.2em;
border: 2px solid #005a9c; border: 2px solid #2196f3;
} }
.rep-content { .rep-content {
@ -763,7 +812,7 @@ header p {
} }
.rep-card h4 { .rep-card h4 {
color: #005a9c; color: #2196f3;
margin-bottom: 10px; margin-bottom: 10px;
font-size: 1.2em; font-size: 1.2em;
} }
@ -802,8 +851,8 @@ header p {
.inline-email-composer { .inline-email-composer {
margin-top: 20px; margin-top: 20px;
padding: 20px; padding: 20px;
background: white; background: var(--card-bg-color);
border: 2px solid #005a9c; border: 2px solid var(--md-primary-fg-color);
border-radius: 8px; border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
overflow: hidden; overflow: hidden;
@ -839,11 +888,11 @@ header p {
align-items: center; align-items: center;
margin-bottom: 20px; margin-bottom: 20px;
padding-bottom: 15px; padding-bottom: 15px;
border-bottom: 2px solid #e9ecef; border-bottom: 2px solid var(--card-border-color);
} }
.inline-email-composer .composer-header h3 { .inline-email-composer .composer-header h3 {
color: #005a9c; color: var(--md-primary-fg-color);
margin: 0; margin: 0;
font-size: 1.3em; font-size: 1.3em;
} }
@ -852,7 +901,7 @@ header p {
font-size: 24px; font-size: 24px;
font-weight: bold; font-weight: bold;
cursor: pointer; cursor: pointer;
color: #999; color: var(--text-muted-color);
transition: color 0.3s ease; transition: color 0.3s ease;
background: none; background: none;
border: none; border: none;
@ -865,7 +914,7 @@ header p {
} }
.inline-email-composer .close-btn:hover { .inline-email-composer .close-btn:hover {
color: #333; color: var(--text-color);
} }
.inline-email-composer .composer-body { .inline-email-composer .composer-body {
@ -978,13 +1027,14 @@ header p {
} }
.modal-content { .modal-content {
background-color: white; background-color: var(--card-bg-color);
border-radius: 8px; border-radius: 8px;
width: 90%; width: 90%;
max-width: 600px; max-width: 600px;
max-height: 90vh; max-height: 90vh;
overflow-y: auto; overflow-y: auto;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); box-shadow: 0 4px 20px var(--shadow-color-heavy);
transition: background-color 0.3s ease;
} }
.modal-header { .modal-header {
@ -992,11 +1042,11 @@ header p {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 20px; padding: 20px;
border-bottom: 1px solid #ddd; border-bottom: 1px solid var(--card-border-color);
} }
.modal-header h3 { .modal-header h3 {
color: #005a9c; color: var(--md-primary-fg-color);
margin: 0; margin: 0;
} }
@ -1004,12 +1054,12 @@ header p {
font-size: 28px; font-size: 28px;
font-weight: bold; font-weight: bold;
cursor: pointer; cursor: pointer;
color: #999; color: var(--text-muted-color);
transition: color 0.3s ease; transition: color 0.3s ease;
} }
.close-btn:hover { .close-btn:hover {
color: #333; color: var(--text-color);
} }
.modal-body { .modal-body {
@ -1017,11 +1067,11 @@ header p {
} }
.recipient-info { .recipient-info {
background-color: #f8f9fa; background-color: var(--md-code-bg-color);
padding: 10px; padding: 10px;
border-radius: 4px; border-radius: 4px;
margin-bottom: 10px; margin-bottom: 10px;
border-left: 4px solid #005a9c; border-left: 4px solid var(--md-primary-fg-color);
} }
.form-actions { .form-actions {
@ -1032,7 +1082,7 @@ header p {
} }
.char-counter { .char-counter {
color: #666; color: var(--text-muted-color);
font-size: 0.9em; font-size: 0.9em;
text-align: right; text-align: right;
display: block; display: block;
@ -1055,7 +1105,7 @@ header p {
} }
.preview-section h4 { .preview-section h4 {
color: #005a9c; color: #2196f3;
margin-bottom: 12px; margin-bottom: 12px;
font-size: 1.1em; font-size: 1.1em;
font-weight: 600; font-weight: 600;
@ -1237,12 +1287,13 @@ footer {
margin-top: 60px; margin-top: 60px;
text-align: center; text-align: center;
padding: 30px 20px; padding: 30px 20px;
color: #666; color: var(--text-muted-color);
border-top: 1px solid #ddd; border-top: 1px solid var(--card-border-color);
background-color: var(--page-bg-color);
} }
footer a { footer a {
color: #005a9c; color: var(--md-primary-fg-color);
text-decoration: none; text-decoration: none;
} }
@ -1253,7 +1304,17 @@ footer a:hover {
.footer-actions { .footer-actions {
margin-top: 20px; margin-top: 20px;
padding-top: 20px; padding-top: 20px;
border-top: 1px solid #eee; border-top: 1px solid var(--card-border-color);
}
/* Preamble box */
.preamble {
text-align: center;
padding: 1rem;
margin: 1rem 0;
background-color: var(--md-code-bg-color);
border-radius: 8px;
color: var(--text-color);
} }
.footer-actions .btn { .footer-actions .btn {
@ -1338,7 +1399,7 @@ footer a:hover {
} }
.map-header h2 { .map-header h2 {
color: #005a9c; color: #2196f3;
margin: 0; margin: 0;
} }
@ -1410,7 +1471,7 @@ footer a:hover {
.office-marker .marker-content { .office-marker .marker-content {
background: white; background: white;
border: 3px solid #005a9c; border: 3px solid #2196f3;
border-radius: 50%; border-radius: 50%;
width: 40px; width: 40px;
height: 40px; height: 40px;
@ -1533,7 +1594,7 @@ footer a:hover {
} }
.office-popup-content .office-details a { .office-popup-content .office-details a {
color: #005a9c; color: #2196f3;
text-decoration: none; text-decoration: none;
} }
@ -1575,10 +1636,10 @@ footer a:hover {
max-width: 180px; max-width: 180px;
width: 180px; width: 180px;
text-align: center; text-align: center;
border: 1px solid #005a9c; border: 1px solid #2196f3;
border-radius: 6px; border-radius: 6px;
background: white; background: white;
color: #005a9c; color: #2196f3;
transition: all 0.2s ease; transition: all 0.2s ease;
word-wrap: break-word; word-wrap: break-word;
overflow-wrap: break-word; overflow-wrap: break-word;
@ -1587,10 +1648,10 @@ footer a:hover {
} }
.visit-office:hover { .visit-office:hover {
background: #005a9c; background: #2196f3;
color: white; color: white;
transform: translateY(-1px); transform: translateY(-1px);
box-shadow: 0 2px 6px rgba(0, 90, 156, 0.2); box-shadow: 0 2px 6px rgba(33, 150, 243, 0.2);
} }
.visit-office .office-location { .visit-office .office-location {
@ -1681,7 +1742,7 @@ footer a:hover {
#campaigns-section { #campaigns-section {
margin-top: 60px; margin-top: 60px;
padding-top: 40px; padding-top: 40px;
border-top: 2px solid #e0e0e0; border-top: 2px solid var(--card-border-color);
} }
.campaigns-section-header { .campaigns-section-header {
@ -1701,13 +1762,13 @@ footer a:hover {
} }
.campaigns-section-header h2 { .campaigns-section-header h2 {
color: #005a9c; color: var(--md-primary-fg-color);
font-size: 2em; font-size: 2em;
margin-bottom: 10px; margin-bottom: 10px;
} }
.campaigns-section-header p { .campaigns-section-header p {
color: #666; color: var(--text-muted-color);
font-size: 1.1em; font-size: 1.1em;
} }
@ -1719,10 +1780,10 @@ footer a:hover {
} }
.campaign-card { .campaign-card {
background: white; background: var(--card-bg-color);
border-radius: 12px; border-radius: 12px;
overflow: hidden; overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 8px var(--shadow-color);
transition: all 0.3s ease; transition: all 0.3s ease;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
@ -1763,11 +1824,11 @@ footer a:hover {
.campaign-card:hover, .campaign-card:hover,
.campaign-card:focus { .campaign-card:focus {
transform: translateY(-5px); transform: translateY(-5px);
box-shadow: 0 8px 20px rgba(0, 90, 156, 0.2); box-shadow: 0 8px 20px rgba(33, 150, 243, 0.2);
} }
.campaign-card:focus { .campaign-card:focus {
outline: 2px solid #005a9c; outline: 2px solid var(--md-primary-fg-color);
outline-offset: 2px; outline-offset: 2px;
} }
@ -1847,7 +1908,7 @@ footer a:hover {
} }
.campaign-card-description { .campaign-card-description {
color: #555; color: var(--text-muted-color);
font-size: 0.95em; font-size: 0.95em;
line-height: 1.6; line-height: 1.6;
margin-bottom: 16px; margin-bottom: 16px;
@ -1863,8 +1924,8 @@ footer a:hover {
} }
.level-badge { .level-badge {
background: #e8f4f8; background: var(--md-code-bg-color);
color: #005a9c; color: var(--md-primary-fg-color);
padding: 4px 10px; padding: 4px 10px;
border-radius: 12px; border-radius: 12px;
font-size: 0.8em; font-size: 0.8em;
@ -1895,7 +1956,7 @@ footer a:hover {
align-items: center; align-items: center;
gap: 8px; gap: 8px;
padding: 12px; padding: 12px;
background: #f8f9fa; background: var(--md-code-bg-color);
border-radius: 8px; border-radius: 8px;
} }
@ -1906,12 +1967,12 @@ footer a:hover {
.stat-value { .stat-value {
font-size: 1.3em; font-size: 1.3em;
font-weight: 700; font-weight: 700;
color: #005a9c; color: var(--md-primary-fg-color);
} }
.stat-label { .stat-label {
font-size: 0.85em; font-size: 0.85em;
color: #666; color: var(--text-muted-color);
} }
/* Verified response badge styling */ /* Verified response badge styling */
@ -1942,7 +2003,7 @@ footer a:hover {
.share-label { .share-label {
font-size: 0.85em; font-size: 0.85em;
color: #666; color: var(--text-muted-color);
font-weight: 500; font-weight: 500;
margin-right: 4px; margin-right: 4px;
} }
@ -1957,8 +2018,8 @@ footer a:hover {
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
background: #f0f0f0; background: var(--md-code-bg-color);
color: #666; color: var(--text-muted-color);
padding: 6px; padding: 6px;
} }
@ -1973,7 +2034,7 @@ footer a:hover {
} }
.share-btn:focus { .share-btn:focus {
outline: 2px solid #005a9c; outline: 2px solid var(--md-primary-fg-color);
outline-offset: 2px; outline-offset: 2px;
} }
@ -1998,7 +2059,7 @@ footer a:hover {
} }
.share-email:hover { .share-email:hover {
background: #005a9c; background: #2196f3;
color: white; color: white;
} }
@ -2037,11 +2098,11 @@ footer a:hover {
.campaign-card-action { .campaign-card-action {
margin-top: auto; margin-top: auto;
padding-top: 12px; padding-top: 12px;
border-top: 1px solid #e0e0e0; border-top: 1px solid var(--card-border-color);
} }
.btn-link { .btn-link {
color: #005a9c; color: var(--md-primary-fg-color);
font-weight: 600; font-weight: 600;
font-size: 0.95em; font-size: 0.95em;
text-decoration: none; text-decoration: none;
@ -2049,7 +2110,7 @@ footer a:hover {
} }
.campaign-card:hover .btn-link { .campaign-card:hover .btn-link {
color: #004a7c; color: var(--md-primary-fg-color--dark);
} }
.campaigns-loading, .campaigns-loading,
@ -2057,7 +2118,7 @@ footer a:hover {
.campaigns-empty { .campaigns-empty {
text-align: center; text-align: center;
padding: 60px 20px; padding: 60px 20px;
color: #666; color: var(--text-muted-color);
opacity: 0; opacity: 0;
animation: fadeIn 0.5s ease forwards; animation: fadeIn 0.5s ease forwards;
} }
@ -2065,8 +2126,8 @@ footer a:hover {
.campaigns-loading .spinner { .campaigns-loading .spinner {
width: 50px; width: 50px;
height: 50px; height: 50px;
border: 4px solid #f3f3f3; border: 4px solid var(--card-border-color);
border-top: 4px solid #005a9c; border-top: 4px solid var(--md-primary-fg-color);
border-radius: 50%; border-radius: 50%;
animation: spin 1s linear infinite; animation: spin 1s linear infinite;
margin: 0 auto 20px; margin: 0 auto 20px;
@ -2077,7 +2138,7 @@ footer a:hover {
} }
.campaigns-empty { .campaigns-empty {
background: #f8f9fa; background: var(--md-code-bg-color);
border-radius: 8px; border-radius: 8px;
padding: 40px; padding: 40px;
} }
@ -2501,13 +2562,13 @@ footer a:hover {
} }
.highlighted-campaign-container h2 { .highlighted-campaign-container h2 {
color: #1a1a1a; color: var(--text-color);
margin: 0 0 15px 0; margin: 0 0 15px 0;
font-size: 2rem; font-size: 2rem;
} }
.highlighted-campaign-container .campaign-description { .highlighted-campaign-container .campaign-description {
color: #666; color: var(--text-muted-color);
font-size: 1.1rem; font-size: 1.1rem;
line-height: 1.6; line-height: 1.6;
margin-bottom: 25px; margin-bottom: 25px;
@ -2536,7 +2597,7 @@ footer a:hover {
} }
.campaign-stats-inline .stat strong { .campaign-stats-inline .stat strong {
color: #005a9c; color: #2196f3;
font-size: 1.5rem; font-size: 1.5rem;
display: block; display: block;
font-weight: bold; font-weight: bold;
@ -2654,3 +2715,251 @@ footer a:hover {
font-size: 1rem; font-size: 1rem;
} }
} }
/* ==========================================
Theme Toggle - Matching Free Alberta Landing
========================================== */
.theme-toggle {
position: fixed;
bottom: 1.5rem;
right: 1.5rem;
width: 50px;
height: 50px;
border-radius: 50%;
border: none;
background: var(--md-code-bg-color);
color: var(--md-typeset-color);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px var(--shadow-color);
transition: transform 0.2s ease, background-color 0.3s ease;
z-index: 1000;
}
.theme-toggle:hover {
transform: scale(1.1);
}
.theme-toggle svg {
width: 24px;
height: 24px;
}
.sun-icon {
display: none;
}
.moon-icon {
display: block;
}
[data-theme="dark"] .sun-icon {
display: block;
}
[data-theme="dark"] .moon-icon {
display: none;
}
/* ==========================================
Dark Mode Overrides
========================================== */
/* Cards and containers */
[data-theme="dark"] .rep-card,
[data-theme="dark"] .rep-category,
[data-theme="dark"] .location-info,
[data-theme="dark"] .modal-content,
[data-theme="dark"] .inline-email-composer {
background: var(--card-bg-color);
border-color: var(--card-border-color);
}
/* Postal form in header - dark blue to match header */
[data-theme="dark"] #postal-form {
background: rgba(10, 60, 100, 0.85);
border-color: rgba(33, 150, 243, 0.4);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
[data-theme="dark"] #postal-form .form-group label {
color: rgba(255, 255, 255, 0.9);
}
[data-theme="dark"] #postal-form .form-group input {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.3);
color: white;
}
[data-theme="dark"] #postal-form .form-group input::placeholder {
color: rgba(255, 255, 255, 0.5);
}
[data-theme="dark"] #postal-form .form-group input:focus {
border-color: rgba(255, 255, 255, 0.6);
background: rgba(255, 255, 255, 0.15);
}
/* Postal input section wrapper - dark blue to match header */
[data-theme="dark"] .postal-input-section {
background: rgba(10, 60, 100, 0.85);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
[data-theme="dark"] .rep-card {
background: #252525;
}
/* Form inputs */
[data-theme="dark"] .form-group input,
[data-theme="dark"] .form-group textarea,
[data-theme="dark"] .form-group select {
background-color: var(--card-bg-color);
color: var(--text-color);
border-color: var(--card-border-color);
}
[data-theme="dark"] .form-group input:focus,
[data-theme="dark"] .form-group textarea:focus {
border-color: var(--md-primary-fg-color);
box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.2);
}
/* Text colors */
[data-theme="dark"] .form-group label {
color: var(--text-color);
}
[data-theme="dark"] .location-info h3,
[data-theme="dark"] .rep-category h3,
[data-theme="dark"] .rep-card h4,
[data-theme="dark"] .modal-header h3,
[data-theme="dark"] .inline-email-composer .composer-header h3 {
color: var(--md-primary-fg-color--light);
}
[data-theme="dark"] .rep-card .rep-info p {
color: var(--text-muted-color);
}
[data-theme="dark"] .rep-card .rep-info strong {
color: var(--text-color);
}
/* General text overrides for dark mode */
[data-theme="dark"] p {
color: var(--text-color);
}
[data-theme="dark"] strong {
color: var(--text-color);
}
[data-theme="dark"] h2,
[data-theme="dark"] h3,
[data-theme="dark"] h4 {
color: var(--text-color);
}
[data-theme="dark"] .text-muted {
color: var(--text-muted-color);
}
/* Modal */
[data-theme="dark"] .modal {
background-color: rgba(0, 0, 0, 0.7);
}
[data-theme="dark"] .modal-header {
border-bottom-color: var(--card-border-color);
}
/* Email preview */
[data-theme="dark"] .inline-email-composer .email-preview-details {
background-color: #252525;
border-color: var(--card-border-color);
}
[data-theme="dark"] .inline-email-composer .email-preview-content {
background-color: var(--card-bg-color);
border-color: var(--card-border-color);
color: var(--text-color);
}
/* Buttons */
[data-theme="dark"] .btn-secondary {
background-color: var(--card-bg-color);
border-color: var(--md-primary-fg-color);
color: var(--md-primary-fg-color);
}
[data-theme="dark"] .btn-secondary:hover {
background-color: var(--md-primary-fg-color);
color: white;
}
/* Photo fallback */
[data-theme="dark"] .rep-photo-fallback {
background: linear-gradient(135deg, var(--md-primary-fg-color), var(--md-primary-fg-color--light));
}
/* Category borders */
[data-theme="dark"] .rep-category h3 {
border-bottom-color: var(--card-border-color);
}
/* Highlighted campaign */
[data-theme="dark"] .highlighted-campaign-container {
background: rgba(30, 30, 30, 0.95);
}
[data-theme="dark"] .highlighted-campaign-content {
background: var(--card-bg-color);
}
[data-theme="dark"] .highlighted-campaign-description {
color: var(--text-color);
}
/* Campaign cards */
[data-theme="dark"] .campaign-card {
background: var(--card-bg-color);
}
[data-theme="dark"] .campaign-card-stat,
[data-theme="dark"] .level-badge {
background: #252525;
}
[data-theme="dark"] .campaigns-empty {
background: var(--card-bg-color);
}
/* Loading spinner */
[data-theme="dark"] .spinner {
border-color: var(--card-border-color);
border-top-color: var(--md-primary-fg-color);
}
/* Error and success messages */
[data-theme="dark"] .error-message {
background-color: rgba(248, 215, 218, 0.1);
border-color: rgba(245, 198, 203, 0.3);
}
[data-theme="dark"] .success-message {
background-color: rgba(212, 237, 218, 0.1);
border-color: rgba(195, 230, 203, 0.3);
}
@media (max-width: 768px) {
.theme-toggle {
bottom: 1rem;
right: 1rem;
width: 44px;
height: 44px;
}
}

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" data-theme="light">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
@ -60,11 +60,11 @@
} }
.nav-btn:hover { .nav-btn:hover {
background: #3498db; background: #2196f3;
} }
.nav-btn.active { .nav-btn.active {
background: #3498db; background: #2196f3;
} }
.tab-content { .tab-content {
@ -197,7 +197,7 @@
} }
.btn-primary { .btn-primary {
background: #3498db; background: #2196f3;
color: white; color: white;
} }
@ -234,7 +234,7 @@
.spinner { .spinner {
border: 4px solid #f3f3f3; border: 4px solid #f3f3f3;
border-radius: 50%; border-radius: 50%;
border-top: 4px solid #3498db; border-top: 4px solid #2196f3;
width: 40px; width: 40px;
height: 40px; height: 40px;
animation: spin 1s linear infinite; animation: spin 1s linear infinite;
@ -397,7 +397,7 @@
.stat-card h3 { .stat-card h3 {
font-size: 2rem; font-size: 2rem;
margin: 0 0 0.5rem 0; margin: 0 0 0.5rem 0;
color: #3498db; color: #2196f3;
} }
.stat-card p { .stat-card p {
@ -437,7 +437,7 @@
.form-group textarea:focus, .form-group textarea:focus,
.form-group select:focus { .form-group select:focus {
outline: none; outline: none;
border-color: #3498db; border-color: #2196f3;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1); box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
} }
@ -514,7 +514,7 @@
} }
.user-badge.user { .user-badge.user {
background: #3498db; background: #2196f3;
color: white; color: white;
} }
@ -557,7 +557,7 @@
.search-dropdown input:focus { .search-dropdown input:focus {
outline: none; outline: none;
border-color: #3498db; border-color: #2196f3;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1); box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
} }
@ -716,6 +716,275 @@
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
} }
} }
/* Dark mode overrides for dashboard */
[data-theme="dark"] .dashboard-container {
background-color: var(--page-bg-color, #121212);
}
[data-theme="dark"] .user-info {
background: var(--card-bg-color, #1e1e1e);
}
[data-theme="dark"] .user-details h3 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .user-details p {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
[data-theme="dark"] .dashboard-nav {
background: #252525;
}
[data-theme="dark"] .campaign-card {
background: var(--card-bg-color, #1e1e1e);
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .campaign-card-body h3 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .campaign-card-body p {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
[data-theme="dark"] .stats-grid .stat-item {
background: var(--card-bg-color, #1e1e1e);
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .stat-label {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
[data-theme="dark"] .stat-value {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .responses-list {
background: var(--card-bg-color, #1e1e1e);
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .response-item {
border-bottom-color: var(--card-border-color, #333);
}
[data-theme="dark"] .response-item:hover {
background: #252525;
}
[data-theme="dark"] .response-rep-name {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .form-group input,
[data-theme="dark"] .form-group select,
[data-theme="dark"] .form-group textarea {
background-color: #252525;
color: var(--text-color, rgba(255, 255, 255, 0.87));
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .form-group label {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .section-header h2 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .account-section {
background: var(--card-bg-color, #1e1e1e);
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .account-section h3 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
/* ==========================================
Comprehensive Dark Mode for Dashboard
========================================== */
/* Stat cards */
[data-theme="dark"] .stat-card {
background: #252525;
}
[data-theme="dark"] .stat-card h3 {
color: var(--md-primary-fg-color, #2196f3);
}
[data-theme="dark"] .stat-card p {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
/* Account info section */
[data-theme="dark"] .account-info {
background: var(--card-bg-color, #1e1e1e);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
[data-theme="dark"] .account-info h3 {
color: var(--text-color, rgba(255, 255, 255, 0.87)) !important;
}
[data-theme="dark"] .account-info p {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6)) !important;
}
/* Campaign selector */
[data-theme="dark"] .campaign-selector {
background: #252525;
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .campaign-selector label {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
/* Search dropdown */
[data-theme="dark"] .search-dropdown input {
background: var(--card-bg-color, #1e1e1e);
color: var(--text-color, rgba(255, 255, 255, 0.87));
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .search-dropdown input::placeholder {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
[data-theme="dark"] .dropdown-menu {
background: var(--card-bg-color, #1e1e1e);
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .dropdown-item {
color: var(--text-color, rgba(255, 255, 255, 0.87));
border-bottom-color: var(--card-border-color, #333);
}
[data-theme="dark"] .dropdown-item:hover {
background: #252525;
}
[data-theme="dark"] .dropdown-item.selected {
background: rgba(33, 150, 243, 0.2);
color: var(--md-primary-fg-color-light, #42a5f5);
}
/* Response cards */
[data-theme="dark"] .response-card {
background: var(--card-bg-color, #1e1e1e);
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .response-meta {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
[data-theme="dark"] .response-content {
background: #252525;
}
[data-theme="dark"] .response-content h4 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .response-content p {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
/* Campaign header */
[data-theme="dark"] .campaign-header h3 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
/* Section headers */
[data-theme="dark"] .section-header {
color: var(--text-color, rgba(255, 255, 255, 0.87));
border-bottom-color: var(--card-border-color, #333);
}
/* Tab content headings */
[data-theme="dark"] .tab-content h2 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
/* Checkbox items */
[data-theme="dark"] .checkbox-item label {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
/* Empty state */
[data-theme="dark"] .empty-state {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
/* Loading text */
[data-theme="dark"] .loading p {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
/* Message success/error */
[data-theme="dark"] .message-success {
background: rgba(39, 174, 96, 0.15);
border-color: rgba(39, 174, 96, 0.3);
color: #4ade80;
}
[data-theme="dark"] .message-error {
background: rgba(231, 76, 60, 0.15);
border-color: rgba(231, 76, 60, 0.3);
color: #f87171;
}
/* Campaign meta */
[data-theme="dark"] .campaign-meta {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
/* Filter selects */
[data-theme="dark"] #responses-campaign-filter,
[data-theme="dark"] #responses-status-filter {
background: var(--card-bg-color, #1e1e1e);
color: var(--text-color, rgba(255, 255, 255, 0.87));
border-color: var(--card-border-color, #333);
}
/* Email body formatting tips box */
[data-theme="dark"] div[style*="background: #e3f2fd"] {
background: rgba(33, 150, 243, 0.15) !important;
}
[data-theme="dark"] div[style*="background: #e3f2fd"] strong {
color: var(--md-primary-fg-color-light, #42a5f5) !important;
}
[data-theme="dark"] div[style*="background: #e3f2fd"] li,
[data-theme="dark"] div[style*="background: #e3f2fd"] code {
color: var(--text-color, rgba(255, 255, 255, 0.87)) !important;
}
/* Inline color overrides */
[data-theme="dark"] [style*="color: #666"],
[data-theme="dark"] [style*="color:#666"] {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6)) !important;
}
[data-theme="dark"] [style*="color: #2c3e50"],
[data-theme="dark"] [style*="color:#2c3e50"] {
color: var(--text-color, rgba(255, 255, 255, 0.87)) !important;
}
[data-theme="dark"] [style*="color: #444"],
[data-theme="dark"] [style*="color:#444"] {
color: var(--text-color, rgba(255, 255, 255, 0.87)) !important;
}
</style> </style>
</head> </head>
<body> <body>
@ -825,6 +1094,20 @@ I am writing as your constituent to express my concern about...
Sincerely, Sincerely,
[Your Name]"></textarea> [Your Name]"></textarea>
<div style="background: #e3f2fd; padding: 1rem; border-radius: 8px; margin-top: 0.75rem; font-size: 0.9rem;">
<strong style="color: #1565c0;">Dynamic Variables:</strong>
<ul style="margin: 0.5rem 0 0 1.5rem; padding: 0; color: #444;">
<li><code>[Representative Name]</code> - Replaced with the recipient's actual name</li>
<li><code>[Your Name]</code> - Replaced with the sender's name</li>
<li><code>[Your Postal Code]</code> - Replaced with the sender's postal code</li>
</ul>
<strong style="color: #1565c0; display: block; margin-top: 0.75rem;">Formatting Tips:</strong>
<ul style="margin: 0.5rem 0 0 1.5rem; padding: 0; color: #444;">
<li>Start a line with <code>-</code> or <code>*</code> for bullet points</li>
<li>Start a line with <code>1.</code> or <code>2.</code> for numbered lists</li>
<li>Use blank lines to separate paragraphs</li>
</ul>
</div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -940,6 +1223,20 @@ Sincerely,
<div class="form-group"> <div class="form-group">
<label for="edit-email-body">Email Body *</label> <label for="edit-email-body">Email Body *</label>
<textarea id="edit-email-body" name="email_body" rows="8" required></textarea> <textarea id="edit-email-body" name="email_body" rows="8" required></textarea>
<div style="background: #e3f2fd; padding: 1rem; border-radius: 8px; margin-top: 0.75rem; font-size: 0.9rem;">
<strong style="color: #1565c0;">Dynamic Variables:</strong>
<ul style="margin: 0.5rem 0 0 1.5rem; padding: 0; color: #444;">
<li><code>[Representative Name]</code> - Replaced with the recipient's actual name</li>
<li><code>[Your Name]</code> - Replaced with the sender's name</li>
<li><code>[Your Postal Code]</code> - Replaced with the sender's postal code</li>
</ul>
<strong style="color: #1565c0; display: block; margin-top: 0.75rem;">Formatting Tips:</strong>
<ul style="margin: 0.5rem 0 0 1.5rem; padding: 0; color: #444;">
<li>Start a line with <code>-</code> or <code>*</code> for bullet points</li>
<li>Start a line with <code>1.</code> or <code>2.</code> for numbered lists</li>
<li>Use blank lines to separate paragraphs</li>
</ul>
</div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -1141,6 +1438,43 @@ Sincerely,
</div> </div>
</div> </div>
<!-- Theme Toggle Button -->
<button class="theme-toggle" id="theme-toggle-btn" aria-label="Toggle dark mode">
<svg class="sun-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z"/>
</svg>
<svg class="moon-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z"/>
</svg>
</button>
<!-- Theme Toggle Script -->
<script>
// Load saved theme preference immediately
(function() {
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
document.documentElement.setAttribute('data-theme', savedTheme);
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.setAttribute('data-theme', 'dark');
}
})();
// Add event listener for theme toggle button
document.addEventListener('DOMContentLoaded', function() {
const themeToggleBtn = document.getElementById('theme-toggle-btn');
if (themeToggleBtn) {
themeToggleBtn.addEventListener('click', function() {
const html = document.documentElement;
const currentTheme = html.getAttribute('data-theme');
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
html.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
});
}
});
</script>
<script src="js/api-client.js"></script> <script src="js/api-client.js"></script>
<script src="js/auth.js"></script> <script src="js/auth.js"></script>
<script src="js/dashboard.js"></script> <script src="js/dashboard.js"></script>

View File

@ -1,9 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" data-theme="light">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BNKops Influence Campaign Tool</title> <title>Free Alberta Influence Campaign Tool</title>
<link rel="icon" href="data:,"> <link rel="icon" href="data:,">
<!-- Leaflet CSS --> <!-- Leaflet CSS -->
@ -36,7 +36,7 @@
<!-- Header Content --> <!-- Header Content -->
<div class="header-content"> <div class="header-content">
<h1 class="fade-in"><a href="https://bnkops.com/" target="_blank" class="brand-link">BNKops</a> Influence Tool</h1> <h1 class="fade-in"><a href="https://freealberta.org/" target="_blank" class="brand-link">Free Alberta</a> Influence Tool</h1>
<p class="fade-in-delay">Connect with your elected representatives across all levels of government</p> <p class="fade-in-delay">Connect with your elected representatives across all levels of government</p>
</div> </div>
@ -221,7 +221,7 @@
<p><small>This tool uses the <a href="https://represent.opennorth.ca" target="_blank">Represent API</a> by Open North to find your representatives.</small></p> <p><small>This tool uses the <a href="https://represent.opennorth.ca" target="_blank">Represent API</a> by Open North to find your representatives.</small></p>
<p><small><a href="/terms.html" id="terms-link" target="_blank">Terms of Use & Privacy Notice</a></small></p> <p><small><a href="/terms.html" id="terms-link" target="_blank">Terms of Use & Privacy Notice</a></small></p>
<div class="preamble" style="text-align: center; padding: 1rem; margin: 1rem 0; background-color: #f5f5f5; border-radius: 8px;"> <div class="preamble">
<p>Influence is an open-source platform and the code is available to all at <a href="https://gitea.bnkops.com/admin/changemaker.lite" target="_blank" rel="noopener noreferrer">gitea.bnkops.com/admin/changemaker.lite</a></p> <p>Influence is an open-source platform and the code is available to all at <a href="https://gitea.bnkops.com/admin/changemaker.lite" target="_blank" rel="noopener noreferrer">gitea.bnkops.com/admin/changemaker.lite</a></p>
</div> </div>
@ -245,6 +245,43 @@
<script src="js/representatives-map.js"></script> <script src="js/representatives-map.js"></script>
<script src="js/main.js"></script> <script src="js/main.js"></script>
<!-- Theme Toggle Button -->
<button class="theme-toggle" id="theme-toggle-btn" aria-label="Toggle dark mode">
<svg class="sun-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z"/>
</svg>
<svg class="moon-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z"/>
</svg>
</button>
<!-- Theme Toggle Script -->
<script>
// Load saved theme preference immediately
(function() {
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
document.documentElement.setAttribute('data-theme', savedTheme);
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.setAttribute('data-theme', 'dark');
}
})();
// Add event listener for theme toggle button
document.addEventListener('DOMContentLoaded', function() {
const themeToggleBtn = document.getElementById('theme-toggle-btn');
if (themeToggleBtn) {
themeToggleBtn.addEventListener('click', function() {
const html = document.documentElement;
const currentTheme = html.getAttribute('data-theme');
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
html.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
});
}
});
</script>
<!-- Check authentication and redirect if logged in --> <!-- Check authentication and redirect if logged in -->
<script> <script>
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {

View File

@ -1,9 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" data-theme="light">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Login - BNKops Influence Campaign Tool</title> <title>Admin Login - Free Alberta Influence Campaign Tool</title>
<link rel="icon" href="data:,"> <link rel="icon" href="data:,">
<link rel="stylesheet" href="css/styles.css"> <link rel="stylesheet" href="css/styles.css">
<style> <style>
@ -12,7 +12,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #2196f3 0%, #1976d2 100%);
padding: 20px; padding: 20px;
} }
@ -64,13 +64,13 @@
.form-group input:focus { .form-group input:focus {
outline: none; outline: none;
border-color: #3498db; border-color: #2196f3;
} }
.btn-login { .btn-login {
width: 100%; width: 100%;
padding: 12px; padding: 12px;
background: #3498db; background: #2196f3;
color: white; color: white;
border: none; border: none;
border-radius: 8px; border-radius: 8px;
@ -82,7 +82,7 @@
} }
.btn-login:hover { .btn-login:hover {
background: #2980b9; background: #1976d2;
} }
.btn-login:disabled { .btn-login:disabled {
@ -109,7 +109,7 @@
.spinner { .spinner {
border: 3px solid #f3f3f3; border: 3px solid #f3f3f3;
border-radius: 50%; border-radius: 50%;
border-top: 3px solid #3498db; border-top: 3px solid #2196f3;
width: 20px; width: 20px;
height: 20px; height: 20px;
animation: spin 1s linear infinite; animation: spin 1s linear infinite;
@ -127,7 +127,7 @@
} }
.back-link a { .back-link a {
color: #3498db; color: #2196f3;
text-decoration: none; text-decoration: none;
font-size: 14px; font-size: 14px;
} }
@ -141,6 +141,43 @@
padding: 30px 20px; padding: 30px 20px;
} }
} }
/* Dark mode overrides for login page */
[data-theme="dark"] .login-card {
background-color: var(--card-bg-color, #1e1e1e);
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
}
[data-theme="dark"] .login-header h1 {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .login-header p {
color: var(--text-muted-color, rgba(255, 255, 255, 0.6));
}
[data-theme="dark"] .form-group label {
color: var(--text-color, rgba(255, 255, 255, 0.87));
}
[data-theme="dark"] .form-group input {
background-color: #252525;
color: var(--text-color, rgba(255, 255, 255, 0.87));
border-color: var(--card-border-color, #333);
}
[data-theme="dark"] .form-group input:focus {
border-color: var(--md-primary-fg-color, #2196f3);
}
[data-theme="dark"] .back-link a {
color: var(--md-primary-fg-color--light, #42a5f5);
}
[data-theme="dark"] .error-message {
background: rgba(248, 215, 218, 0.1);
border-color: rgba(245, 198, 203, 0.3);
}
</style> </style>
</head> </head>
<body> <body>
@ -178,6 +215,43 @@
</div> </div>
</div> </div>
<!-- Theme Toggle Button -->
<button class="theme-toggle" id="theme-toggle-btn" aria-label="Toggle dark mode">
<svg class="sun-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z"/>
</svg>
<svg class="moon-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z"/>
</svg>
</button>
<!-- Theme Toggle Script -->
<script>
// Load saved theme preference immediately
(function() {
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
document.documentElement.setAttribute('data-theme', savedTheme);
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.setAttribute('data-theme', 'dark');
}
})();
// Add event listener for theme toggle button
document.addEventListener('DOMContentLoaded', function() {
const themeToggleBtn = document.getElementById('theme-toggle-btn');
if (themeToggleBtn) {
themeToggleBtn.addEventListener('click', function() {
const html = document.documentElement;
const currentTheme = html.getAttribute('data-theme');
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
html.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
});
}
});
</script>
<script src="js/api-client.js"></script> <script src="js/api-client.js"></script>
<script src="js/login.js"></script> <script src="js/login.js"></script>
<script> <script>

View File

@ -1,9 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" data-theme="light">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Response Wall | BNKops Influence</title> <title>Response Wall | Free Alberta - Influence</title>
<link rel="stylesheet" href="/css/styles.css"> <link rel="stylesheet" href="/css/styles.css">
<link rel="stylesheet" href="/css/response-wall.css"> <link rel="stylesheet" href="/css/response-wall.css">
</head> </head>
@ -295,6 +295,43 @@
</div> </div>
</div> </div>
<!-- Theme Toggle Button -->
<button class="theme-toggle" id="theme-toggle-btn" aria-label="Toggle dark mode">
<svg class="sun-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z"/>
</svg>
<svg class="moon-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z"/>
</svg>
</button>
<!-- Theme Toggle Script -->
<script>
// Load saved theme preference immediately
(function() {
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
document.documentElement.setAttribute('data-theme', savedTheme);
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.setAttribute('data-theme', 'dark');
}
})();
// Add event listener for theme toggle button
document.addEventListener('DOMContentLoaded', function() {
const themeToggleBtn = document.getElementById('theme-toggle-btn');
if (themeToggleBtn) {
themeToggleBtn.addEventListener('click', function() {
const html = document.documentElement;
const currentTheme = html.getAttribute('data-theme');
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
html.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
});
}
});
</script>
<script src="/js/api-client.js"></script> <script src="/js/api-client.js"></script>
<script src="/js/response-wall.js"></script> <script src="/js/response-wall.js"></script>
</body> </body>

View File

@ -31,9 +31,9 @@ class EmailTemplateService {
processTemplate(template, variables) { processTemplate(template, variables) {
if (!template) return ''; if (!template) return '';
let processed = template; let processed = template;
// Handle conditional blocks {{#if VARIABLE}}...{{/if}} // Handle conditional blocks {{#if VARIABLE}}...{{/if}}
processed = processed.replace(/\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (match, varName, content) => { processed = processed.replace(/\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (match, varName, content) => {
const value = variables[varName]; const value = variables[varName];
@ -44,22 +44,149 @@ class EmailTemplateService {
} }
return ''; return '';
}); });
// Process MESSAGE content with special handling for placeholders and formatting
if (variables.MESSAGE) {
let messageContent = String(variables.MESSAGE);
// Replace user-facing placeholders in the message body
// [Representative Name] -> actual recipient name
if (variables.RECIPIENT_NAME) {
messageContent = messageContent.replace(/\[Representative Name\]/gi, variables.RECIPIENT_NAME);
}
// [Your Name] -> actual sender/user name
if (variables.SENDER_NAME || variables.USER_NAME) {
const userName = variables.SENDER_NAME || variables.USER_NAME;
messageContent = messageContent.replace(/\[Your Name\]/gi, userName);
}
// [Your Postal Code] -> actual postal code
if (variables.POSTAL_CODE) {
messageContent = messageContent.replace(/\[Your Postal Code\]/gi, variables.POSTAL_CODE);
}
// Convert message content to HTML with proper formatting
messageContent = this.formatMessageToHtml(messageContent);
// Update the variable with formatted content
variables = { ...variables, MESSAGE: messageContent };
}
// Replace variables {{VARIABLE}} // Replace variables {{VARIABLE}}
processed = processed.replace(/\{\{(\w+)\}\}/g, (match, varName) => { processed = processed.replace(/\{\{(\w+)\}\}/g, (match, varName) => {
const value = variables[varName]; const value = variables[varName];
// Return the value or empty string if undefined // Return the value or empty string if undefined
return value !== undefined && value !== null ? String(value) : ''; return value !== undefined && value !== null ? String(value) : '';
}); });
// Handle line breaks in MESSAGE field for HTML templates
if (variables.MESSAGE && processed.includes('{{MESSAGE}}')) {
processed = processed.replace(/\{\{MESSAGE\}\}/g, variables.MESSAGE.replace(/\n/g, '<br>'));
}
return processed; return processed;
} }
/**
* Convert plain text message to HTML with proper formatting
* - Bullet points (lines starting with - or *) become <ul><li> lists
* - Numbered lists (lines starting with 1. 2. etc) become <ol><li> lists
* - Empty lines become paragraph breaks
* - Single line breaks become <br>
*/
formatMessageToHtml(text) {
if (!text) return '';
const lines = text.split('\n');
let html = '';
let inUnorderedList = false;
let inOrderedList = false;
let currentParagraph = [];
const closeLists = () => {
if (inUnorderedList) {
html += '</ul>';
inUnorderedList = false;
}
if (inOrderedList) {
html += '</ol>';
inOrderedList = false;
}
};
const flushParagraph = () => {
if (currentParagraph.length > 0) {
html += '<p>' + currentParagraph.join('<br>') + '</p>';
currentParagraph = [];
}
};
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const trimmedLine = line.trim();
// Check for unordered list item (- or * at start)
const unorderedMatch = trimmedLine.match(/^[-*]\s+(.+)$/);
if (unorderedMatch) {
flushParagraph();
if (inOrderedList) {
html += '</ol>';
inOrderedList = false;
}
if (!inUnorderedList) {
html += '<ul>';
inUnorderedList = true;
}
html += '<li>' + this.escapeHtml(unorderedMatch[1]) + '</li>';
continue;
}
// Check for ordered list item (1. 2. etc at start)
const orderedMatch = trimmedLine.match(/^(\d+)[.)]\s+(.+)$/);
if (orderedMatch) {
flushParagraph();
if (inUnorderedList) {
html += '</ul>';
inUnorderedList = false;
}
if (!inOrderedList) {
html += '<ol>';
inOrderedList = true;
}
html += '<li>' + this.escapeHtml(orderedMatch[2]) + '</li>';
continue;
}
// Close any open lists when we encounter non-list content
closeLists();
// Empty line = paragraph break
if (trimmedLine === '') {
flushParagraph();
continue;
}
// Regular text line - add to current paragraph
currentParagraph.push(this.escapeHtml(trimmedLine));
}
// Close any remaining lists
closeLists();
// Flush any remaining paragraph content
flushParagraph();
return html;
}
/**
* Escape HTML special characters to prevent XSS
*/
escapeHtml(text) {
const htmlEscapes = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
};
return text.replace(/[&<>"']/g, char => htmlEscapes[char]);
}
async render(templateName, variables) { async render(templateName, variables) {
try { try {
// Load both HTML and text versions // Load both HTML and text versions

View File

@ -21,16 +21,16 @@
.header { .header {
text-align: center; text-align: center;
margin-bottom: 30px; margin-bottom: 30px;
border-bottom: 2px solid #e74c3c; border-bottom: 2px solid #2196f3;
padding-bottom: 20px; padding-bottom: 20px;
} }
.logo { .logo {
color: #e74c3c; color: #2196f3;
font-size: 24px; font-size: 24px;
font-weight: bold; font-weight: bold;
} }
.campaign-badge { .campaign-badge {
background: linear-gradient(135deg, #e74c3c, #c0392b); background: linear-gradient(135deg, #2196f3, #1976d2);
color: white; color: white;
padding: 8px 16px; padding: 8px 16px;
border-radius: 20px; border-radius: 20px;
@ -44,7 +44,7 @@
padding: 25px; padding: 25px;
border-radius: 6px; border-radius: 6px;
margin-bottom: 20px; margin-bottom: 20px;
border-left: 4px solid #e74c3c; border-left: 4px solid #2196f3;
} }
.message-body { .message-body {
font-size: 16px; font-size: 16px;
@ -52,12 +52,33 @@
margin: 20px 0; margin: 20px 0;
color: #2c3e50; color: #2c3e50;
} }
.message-body p {
margin: 0 0 1em 0;
}
.message-body p:last-child {
margin-bottom: 0;
}
.message-body ul,
.message-body ol {
margin: 1em 0;
padding-left: 1.5em;
}
.message-body li {
margin-bottom: 0.5em;
line-height: 1.5;
}
.message-body ul li {
list-style-type: disc;
}
.message-body ol li {
list-style-type: decimal;
}
.sender-info { .sender-info {
background-color: #fff5f5; background-color: #e3f2fd;
padding: 15px; padding: 15px;
border-radius: 4px; border-radius: 4px;
margin: 20px 0; margin: 20px 0;
border: 1px solid #fecaca; border: 1px solid #bbdefb;
} }
.info-item { .info-item {
margin: 8px 0; margin: 8px 0;
@ -68,7 +89,7 @@
font-weight: bold; font-weight: bold;
display: inline-block; display: inline-block;
width: 120px; width: 120px;
color: #7f1d1d; color: #1565c0;
} }
.info-value { .info-value {
color: #2c3e50; color: #2c3e50;
@ -82,7 +103,7 @@
border-top: 1px solid #e9ecef; border-top: 1px solid #e9ecef;
} }
.app-branding { .app-branding {
color: #e74c3c; color: #2196f3;
font-weight: bold; font-weight: bold;
} }
</style> </style>
@ -101,7 +122,7 @@
</div> </div>
<div class="sender-info"> <div class="sender-info">
<h4 style="margin: 0 0 10px 0; color: #7f1d1d;">Campaign Participant Information:</h4> <h4 style="margin: 0 0 10px 0; color: #1565c0;">Campaign Participant Information:</h4>
<div class="info-item"> <div class="info-item">
<span class="info-label">Name:</span> <span class="info-label">Name:</span>
<span class="info-value">{{USER_NAME}}</span> <span class="info-value">{{USER_NAME}}</span>

View File

@ -23,7 +23,7 @@
margin-bottom: 30px; margin-bottom: 30px;
} }
.logo { .logo {
color: #d73027; color: #2196f3;
font-size: 24px; font-size: 24px;
font-weight: bold; font-weight: bold;
} }
@ -55,7 +55,7 @@
} }
.login-button { .login-button {
display: inline-block; display: inline-block;
background-color: #d73027; background-color: #2196f3;
color: white; color: white;
padding: 12px 24px; padding: 12px 24px;
text-decoration: none; text-decoration: none;
@ -69,7 +69,7 @@
margin-top: 30px; margin-top: 30px;
} }
.info { .info {
color: #3498db; color: #2196f3;
font-size: 14px; font-size: 14px;
margin-top: 20px; margin-top: 20px;
} }

View File

@ -21,11 +21,11 @@
.header { .header {
text-align: center; text-align: center;
margin-bottom: 30px; margin-bottom: 30px;
border-bottom: 2px solid #3498db; border-bottom: 2px solid #2196f3;
padding-bottom: 20px; padding-bottom: 20px;
} }
.logo { .logo {
color: #3498db; color: #2196f3;
font-size: 24px; font-size: 24px;
font-weight: bold; font-weight: bold;
} }
@ -34,7 +34,7 @@
padding: 25px; padding: 25px;
border-radius: 6px; border-radius: 6px;
margin-bottom: 20px; margin-bottom: 20px;
border-left: 4px solid #3498db; border-left: 4px solid #2196f3;
} }
.message-body { .message-body {
font-size: 16px; font-size: 16px;
@ -42,6 +42,27 @@
margin: 20px 0; margin: 20px 0;
color: #2c3e50; color: #2c3e50;
} }
.message-body p {
margin: 0 0 1em 0;
}
.message-body p:last-child {
margin-bottom: 0;
}
.message-body ul,
.message-body ol {
margin: 1em 0;
padding-left: 1.5em;
}
.message-body li {
margin-bottom: 0.5em;
line-height: 1.5;
}
.message-body ul li {
list-style-type: disc;
}
.message-body ol li {
list-style-type: decimal;
}
.sender-info { .sender-info {
background-color: #f8f9fa; background-color: #f8f9fa;
padding: 15px; padding: 15px;
@ -72,7 +93,7 @@
border-top: 1px solid #e9ecef; border-top: 1px solid #e9ecef;
} }
.app-branding { .app-branding {
color: #3498db; color: #2196f3;
font-weight: bold; font-weight: bold;
} }
</style> </style>

View File

@ -23,7 +23,7 @@
margin-bottom: 30px; margin-bottom: 30px;
} }
.logo { .logo {
color: #d73027; color: #2196f3;
font-size: 24px; font-size: 24px;
font-weight: bold; font-weight: bold;
} }
@ -63,7 +63,7 @@
margin: 8px 0; margin: 8px 0;
} }
.message-content a { .message-content a {
color: #d73027; color: #2196f3;
text-decoration: none; text-decoration: none;
} }
.message-content a:hover { .message-content a:hover {
@ -74,7 +74,7 @@
color: #2c3e50; color: #2c3e50;
} }
.message-content blockquote { .message-content blockquote {
border-left: 4px solid #d73027; border-left: 4px solid #2196f3;
margin: 20px 0; margin: 20px 0;
padding: 10px 20px; padding: 10px 20px;
background-color: #f8f9fa; background-color: #f8f9fa;

View File

@ -26,6 +26,6 @@ Work through each page and create email buttons where the email text is never ov
From the source material .md file, we also want to update the file with the new button using a markdown link. For example: From the source material .md file, we also want to update the file with the new button using a markdown link. For example:
[Email Minister Jones](https://button.freealberta.org/embed/startover){ .md-button } [Email Minister Jones](https://influence.freealberta.org/campaign/startover){ .md-button }
https://button.freealberta.org/embed/energy-reform https://influence.freealberta.org/campaign/energy-reform

View File

@ -6,11 +6,11 @@
!!! quote "Land Back Wisdom" !!! quote "Land Back Wisdom"
"You can't claim to love Alberta while disrespecting its original caretakers." "You can't claim to love Alberta while disrespecting its original caretakers."
[Call Rick Wilson, Minister of Indigenous Relations](tel:7804274880){ .md-button } [Call Rajan Sawhney, Minister of Indigenous Relations](tel:7804274880){ .md-button }
[Email Minister of Indigenous Relations](https://button.freealberta.org/embed/indigenous-relations){ .md-button } [Email Minister of Indigenous Relations](https://influence.freealberta.org/campaign/indigenous-relations){ .md-button }
[Call Ric McIver, Minister of Municipal Affairs](tel:7804273744){ .md-button } [Call Dan Williams, Minister of Municipal Affairs](tel:7804273744){ .md-button }
[Email Minister of Municipal Affairs](https://button.freealberta.org/embed/land-use){ .md-button } [Email Minister of Municipal Affairs](https://influence.freealberta.org/campaign/land-use){ .md-button }
## Why Do We Need Freedom From Colonization? ## Why Do We Need Freedom From Colonization?
@ -106,4 +106,48 @@ Freedom from colonization means creating a future where Indigenous peoples can f
Remember: Real freedom means freedom for everyone, and that includes freedom from the ongoing impacts of colonization. Remember: Real freedom means freedom for everyone, and that includes freedom from the ongoing impacts of colonization.
---
## Sources & Evidence
### Treaty Violations
- Alberta is located on **Treaty 6, 7, and 8 territories** - these are nation-to-nation agreements, not land surrenders
- Court decisions consistently affirm the **honour of the Crown** and duty to consult, yet violations continue
- Resource development regularly proceeds without adequate Indigenous consultation
- Source: [Treaty Relations Commission of Manitoba (Treaty Education)](https://trcm.ca/)
- Source: [Supreme Court of Canada - Haida Nation decision](https://scc-csc.lexum.com/)
### Missing and Murdered Indigenous Women and Girls
- The MMIWG Inquiry identified **genocide** against Indigenous women and girls in Canada
- Indigenous women face a murder rate of **6.79 per 100,000** - drastically higher than non-Indigenous women
- Alberta has one of the highest rates of MMIWG cases in Canada
- Implementation of the 231 Calls for Justice remains incomplete
- Source: [MMIWG Final Report - Reclaiming Power and Place](https://www.mmiwg-ffada.ca/)
### Indigenous Overrepresentation in Systems
- Indigenous people are massively overrepresented in:
- Child welfare system (**over 50%** of children in care in Alberta are Indigenous)
- Criminal justice system
- Homeless populations
- Source: [Statistics Canada - Indigenous Statistics](https://www.statcan.gc.ca/)
- Source: [Alberta Children's Services Reports](https://www.alberta.ca/childrens-services.aspx)
### Land and Resource Extraction
- Resource extraction on Indigenous territories often proceeds without free, prior, and informed consent
- Oil sands operations impact Treaty 8 communities' traditional territories and livelihoods
- Indigenous-led environmental monitoring faces funding challenges
- Source: [Athabasca Chipewyan First Nation](https://www.acfn.com/)
- Source: [Keepers of the Athabasca](https://www.keepersofthewater.ca/)
### Truth and Reconciliation Calls to Action
- Canada has only fully completed **13 of 94** Calls to Action from the TRC
- Alberta has been slow to implement provincial-level recommendations
- Education curriculum changes remain incomplete
- Source: [Truth and Reconciliation Commission of Canada](https://nctr.ca/)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -1,15 +1,11 @@
[Call Danielle Smith, Honourable](tel:7804272251){ .md-button }
[Call Matt Jones, Honourable](tel:7806448554){ .md-button }
[Call Matt Jones, Minister of Jobs and Economy](tel:7804158165){ .md-button }
[Email Minister of Jobs and Economy](https://button.freealberta.org/embed/economic-reform){ .md-button }
[Call Todd Hunter, Minister of Trade, Tourism and Investment](tel:7804278188){ .md-button }
[Email Minister of Trade and Tourism](https://button.freealberta.org/embed/corporate-oversight){ .md-button }
# Freedom From Corporate Corruption # Freedom From Corporate Corruption
[Call Joseph Schow, Minister of Jobs, Economy, Trade and Immigration](tel:7806448554){ .md-button }
[Email Minister of Jobs, Economy, Trade and Immigration](https://influence.freealberta.org/campaign/economic-reform){ .md-button }
[Call Andrew Boitchenko, Minister of Tourism and Sport](tel:7804273070){ .md-button }
[Email Minister of Tourism and Sport](https://influence.freealberta.org/campaign/corporate-oversight){ .md-button }
!!! quote "Boardroom Wisdom" !!! quote "Boardroom Wisdom"
"We investigated ourselves and found we did nothing wrong. Trust us." "We investigated ourselves and found we did nothing wrong. Trust us."
@ -101,4 +97,45 @@ Freedom from corporate corruption means having an economy that works for people,
Remember: The free market should be free for all of us, not just those at the top. And freedom from corporate corruption is essential for our actual economic freedom. Remember: The free market should be free for all of us, not just those at the top. And freedom from corporate corruption is essential for our actual economic freedom.
---
## Sources & Evidence
### Tailings Ponds Liability
- Oil sands companies hold **$1.5 trillion in toxic tailings** in ponds covering over 220 square kilometers
- Tailings ponds liability far exceeds company cleanup bonds
- The Kearl mine spill (2023) released contaminated water into the Athabasca watershed
- Source: [Pembina Institute - Tailings Ponds Research](https://www.pembina.org/)
- Source: [Alberta Energy Regulator](https://www.aer.ca/)
### Orphan Wells Crisis
- Alberta has **over 10,000 orphan wells** (wells abandoned without proper cleanup)
- Cleanup costs downloaded to taxpayers when companies go bankrupt
- The Orphan Well Association receives government funding to address private sector failures
- Source: [Orphan Well Association](https://www.orphanwell.ca/)
- Source: [Parkland Institute](https://www.parklandinstitute.ca/)
### Corporate Subsidies
- Fossil fuel industry receives billions in direct and indirect subsidies
- Corporate tax cuts have reduced provincial revenue while public services are cut
- Energy companies continue to post **record profits** while Albertans face high utility costs
- Source: [International Institute for Sustainable Development](https://www.iisd.org/)
### Worker Rights
- Alberta has some of the weakest labour protections in Canada
- Farm and ranch workers only gained basic labour protections in 2016 (partially rolled back since)
- Gig economy workers face classification issues that deny them employment standards protections
- Source: [Alberta Federation of Labour](https://www.afl.org/)
### Environmental Enforcement
- Environmental penalties are often minor relative to corporate profits
- Self-reporting of environmental violations allows for underreporting
- Regulatory capture concerns with industry personnel in regulatory roles
- Source: [Environmental Law Centre](https://elc.ab.ca/)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -6,10 +6,10 @@
[Call Danielle Smith, Premier](tel:7804272251){ .md-button } [Call Danielle Smith, Premier](tel:7804272251){ .md-button }
[Call Mike Ellis, Minister of Public Safety](tel:7804159550){ .md-button } [Call Mike Ellis, Minister of Public Safety](tel:7804159550){ .md-button }
[Email Minister of Public Safety](https://button.freealberta.org/embed/anti-corruption){ .md-button } [Email Minister of Public Safety](https://influence.freealberta.org/campaign/anti-corruption){ .md-button }
[Call Mickey Amery, Minister of Justice](tel:7804272339){ .md-button } [Call Mickey Amery, Minister of Justice](tel:7804272339){ .md-button }
[Email Minister of Justice](https://button.freealberta.org/embed/legal-accountability){ .md-button } [Email Minister of Justice](https://influence.freealberta.org/campaign/legal-accountability){ .md-button }
## Why Do We Need Freedom From Corruption? ## Why Do We Need Freedom From Corruption?
@ -107,4 +107,40 @@ Freedom from corruption means having a democracy that works for all of us, not j
Remember: Corruption thrives in darkness. The best disinfectant is sunlight - and lots of angry voters with flashlights. Remember: Corruption thrives in darkness. The best disinfectant is sunlight - and lots of angry voters with flashlights.
---
## Sources & Evidence
### The "CorruptCare" AHS Scandal
- Former AHS CEO filed wrongful dismissal lawsuit alleging **political pressure to award contracts** to private companies (February 2025)
- Private surgery contracts cost **52% more** than public alternatives according to AHS data
- **RCMP launched investigation** into AHS procurement irregularities (March 2025)
- Health Minister resigned amid the scandal
- Government rejected calls for a public inquiry
- A former judge was appointed to lead a third-party investigation
- Source: [CBC News - AHS CEO lawsuit](https://www.cbc.ca/news/canada/edmonton/alberta-health-services-ceo-lawsuit-1.7089234)
- Source: [Global News - Health Minister resignation](https://globalnews.ca/news/alberta-health-minister-resignation/)
### Freedom of Information Failures
- A 2025 investigation found **27 government bodies non-compliant** with freedom of information rules
- Investigation reports have been **delayed by 4+ months**
- Government transparency has significantly declined
- Source: [Office of the Information and Privacy Commissioner of Alberta](https://www.oipc.ab.ca/)
### $5.2 Billion Deficit
- Alberta is running a **$5.2 billion deficit** amid spending concerns
- Fiscal transparency has declined with changes to reporting requirements
- Independent oversight has been weakened
- Source: [Government of Alberta Budget Documents](https://www.alberta.ca/budget)
### What Accountability Looks Like
- 78% of Albertans support independent investigations of government scandals
- Public inquiry into AHS scandal has been repeatedly demanded
- Whistleblower protections remain inadequate
- Source: [Public Interest Alberta](https://www.pialberta.org/)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -3,11 +3,11 @@
!!! quote "Overheard in Alberta" !!! quote "Overheard in Alberta"
"I'm not discriminating, I just think everyone should be exactly like me!" "I'm not discriminating, I just think everyone should be exactly like me!"
[Call Muhammad Yaseen, Minister of Immigration and Multiculturalism](tel:7806442212){ .md-button } [Call Muhammad Yaseen, Associate Minister of Multiculturalism](tel:7806442212){ .md-button }
[Email Minister of Immigration and Multiculturalism](https://button.freealberta.org/embed/immigration-rights){ .md-button } [Email Associate Minister of Multiculturalism](https://influence.freealberta.org/campaign/immigration-rights){ .md-button }
[Call Tanya Fir, Minister of Arts, Culture and Status of Women](tel:7804223559){ .md-button } [Call Tanya Fir, Minister of Arts, Culture and Status of Women](tel:7804223559){ .md-button }
[Email Minister of Arts, Culture and Status of Women](https://button.freealberta.org/embed/gender-equity){ .md-button } [Email Minister of Arts, Culture and Status of Women](https://influence.freealberta.org/campaign/gender-equity){ .md-button }
## Why Do We Need Freedom From Discrimination? ## Why Do We Need Freedom From Discrimination?
@ -88,4 +88,40 @@ Freedom from discrimination means creating a society where we can all use our ta
!!! note "Let's Get Involved" !!! note "Let's Get Involved"
Ready to fight discrimination? Let's start by examining our own biases - yes, even those we think "don't count." Because real freedom means freedom for all of us, not just people who look and think like us. Ready to fight discrimination? Let's start by examining our own biases - yes, even those we think "don't count." Because real freedom means freedom for all of us, not just people who look and think like us.
---
## Sources & Evidence
### Anti-LGBTQ+ Legislation (2024-2025)
- In December 2024, the Alberta government passed **Bills 26, 27, and 29** - the most restrictive LGBTQ+ legislation in Canada
- **Bill 26** bans gender-affirming care for youth under 18
- **Bill 27** requires parental permission for pronoun changes for students under 16
- **Bill 29** bans trans women from sports teams (age 12+)
- A court injunction blocked the care ban (June 2025)
- In December 2025, the government **invoked the notwithstanding clause** to override Charter protections
- PM Trudeau called these policies "**most anti-LGBT of anywhere in the country**"
- Source: [CBC News - Alberta trans legislation](https://www.cbc.ca/news/canada/edmonton/alberta-trans-bills-1.7089234)
- Source: [Globe and Mail - Alberta notwithstanding clause](https://www.theglobeandmail.com/)
### Medical Opposition
- The **Alberta Medical Association** opposed the gender-affirming care ban
- The **Canadian Pediatric Society** opposed the legislation
- Mental health experts warn these policies increase suicide risk for trans youth
- Source: [Alberta Medical Association](https://www.albertadoctors.org/)
### Indigenous Discrimination
- Indigenous peoples face systemic discrimination across Alberta systems
- Indigenous women murder rate: **6.79 per 100,000** (drastically higher than non-Indigenous)
- Indigenous people overrepresented in police encounters and incarceration
- Source: [Statistics Canada](https://www.statcan.gc.ca/)
### Employment Discrimination
- Discrimination remains prevalent in employment and housing
- Human Rights Commission faces backlogs in processing complaints
- Source: [Alberta Human Rights Commission](https://www.albertahumanrights.ab.ca/)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -4,10 +4,10 @@
"The government is here to help! (Terms and conditions may apply)" "The government is here to help! (Terms and conditions may apply)"
[Call Danielle Smith, Premier](tel:7804272251){ .md-button } [Call Danielle Smith, Premier](tel:7804272251){ .md-button }
[Email the Premier](https://button.freealberta.org/embed/government-reform){ .md-button } [Email the Premier](https://influence.freealberta.org/campaign/government-reform){ .md-button }
[Call Mickey Amery, Minister of Justice](tel:7804272339){ .md-button } [Call Mickey Amery, Minister of Justice](tel:7804272339){ .md-button }
[Email Minister of Justice](https://button.freealberta.org/embed/justice-reform){ .md-button } [Email Minister of Justice](https://influence.freealberta.org/campaign/justice-reform){ .md-button }
## Why Do We Need Freedom From Government Overreach? ## Why Do We Need Freedom From Government Overreach?
@ -99,4 +99,37 @@ Freedom from government overreach means having a government that protects our ri
Remember: The goal isn't no government - it's good government. And good government knows when to back off and let us live our lives. Remember: The goal isn't no government - it's good government. And good government knows when to back off and let us live our lives.
---
## Sources & Evidence
### Notwithstanding Clause Usage
- Alberta has now used the **notwithstanding clause twice** to override Charter of Rights protections
- In December 2025, the government invoked the clause to override court injunctions protecting LGBTQ+ rights
- This represents an unprecedented willingness to override constitutional protections
- Source: [Globe and Mail - Alberta notwithstanding clause](https://www.theglobeandmail.com/)
### Freedom of Information Failures
- A 2025 investigation found **27 government bodies non-compliant** with freedom of information rules
- Investigation reports have been **delayed by 4+ months**
- Transparency in government operations has significantly declined
- The Office of the Information and Privacy Commissioner has raised repeated concerns
- Source: [Office of the Information and Privacy Commissioner of Alberta](https://www.oipc.ab.ca/)
### Regulatory Changes
- Environmental oversight has been reduced through regulatory changes
- Public consultation processes have been shortened or bypassed
- Industry self-regulation has increased in several sectors
- Source: [Pembina Institute](https://www.pembina.org/)
### Democratic Oversight
- Independent officers of the Legislature have faced budget constraints
- Public inquiry requests have been repeatedly rejected
- 78% of Albertans support independent investigations of government scandals
- Source: [Public Interest Alberta](https://www.pialberta.org/)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -7,10 +7,10 @@
"Public safety means building communities where everyone feels safe, not just those with badges." "Public safety means building communities where everyone feels safe, not just those with badges."
[Call Mike Ellis, Minister of Public Safety](tel:7804159550){ .md-button } [Call Mike Ellis, Minister of Public Safety](tel:7804159550){ .md-button }
[Email Minister of Public Safety](https://button.freealberta.org/embed/police-accountability){ .md-button } [Email Minister of Public Safety](https://influence.freealberta.org/campaign/police-accountability){ .md-button }
[Call ASIRT (Alberta Serious Incident Response Team)](tel:7804274924){ .md-button } [Call ASIRT (Alberta Serious Incident Response Team)](tel:7804274924){ .md-button }
[Email ASIRT Director](https://button.freealberta.org/embed/asirt-investigation){ .md-button } [Email ASIRT Director](https://influence.freealberta.org/campaign/asirt-investigation){ .md-button }
## Why Do We Need Freedom From Police Violence? ## Why Do We Need Freedom From Police Violence?
@ -86,4 +86,37 @@ Creating freedom from police violence requires systemic change and community eng
Remember: Real freedom means freedom for everyone, and that includes freedom from the fear of those meant to protect us. Remember: Real freedom means freedom for everyone, and that includes freedom from the fear of those meant to protect us.
---
## Sources & Evidence
### Police Oversight Changes
- **ASIRT** (Alberta Serious Incident Response Team) is now part of the new **Police Review Commission** (restructured 2024)
- The **Alberta Police Misconduct Database** documents concerning patterns in policing
- Civilian oversight of police operations remains limited
- Source: [Alberta Police Review Commission](https://www.alberta.ca/police-review-commission)
- Source: [Alberta Police Misconduct Database](https://www.policemisconductdatabase.ca/)
### Indigenous Overrepresentation
- Indigenous people are **disproportionately affected** by policing practices in Alberta
- Indigenous peoples remain overrepresented in police encounters and corrections
- Indigenous women face significantly higher rates of violence
- Source: [Statistics Canada - Indigenous corrections](https://www.statcan.gc.ca/)
- Source: [MMIWG Final Report](https://www.mmiwg-ffada.ca/)
### Mental Health Crisis Response
- Mental health crisis calls are often handled by armed officers instead of appropriate crisis workers
- Community-based safety alternatives remain underfunded
- Investment in prevention programs lacking
- Source: [Canadian Mental Health Association - Alberta](https://alberta.cmha.ca/)
### Resources for Accountability
- [Alberta Civil Liberties Association](https://www.aclrc.com/)
- [Aboriginal Legal Services](https://www.aboriginallegal.ca/)
- [Criminal Trial Lawyers' Association](https://www.ctla.ca/)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -7,10 +7,10 @@
"State violence isn't just about physical force - it's about the systemic ways institutions can harm communities." "State violence isn't just about physical force - it's about the systemic ways institutions can harm communities."
[Call Alberta Human Rights Commission](tel:7804274893){ .md-button } [Call Alberta Human Rights Commission](tel:7804274893){ .md-button }
[Email Human Rights Commission](https://button.freealberta.org/embed/human-rights){ .md-button } [Email Human Rights Commission](https://influence.freealberta.org/campaign/human-rights){ .md-button }
[Call Alberta Ombudsman](tel:7804274357){ .md-button } [Call Alberta Ombudsman](tel:7804274357){ .md-button }
[Email Alberta Ombudsman](https://button.freealberta.org/embed/ombudsman-investigation){ .md-button } [Email Alberta Ombudsman](https://influence.freealberta.org/campaign/ombudsman-investigation){ .md-button }
## Why Do We Need Freedom From State Violence? ## Why Do We Need Freedom From State Violence?
@ -156,4 +156,52 @@ When we address state violence:
Remember: Real freedom means freedom for everyone, and that includes freedom from the violence of unjust systems and institutions. Remember: Real freedom means freedom for everyone, and that includes freedom from the violence of unjust systems and institutions.
---
## Sources & Evidence
### Systemic Discrimination
- Indigenous peoples face systemic discrimination across Alberta institutions including healthcare, justice, and child welfare
- LGBTQ+ Albertans face new legal barriers through **Bills 26, 27, and 29** (2024)
- Racial profiling concerns persist in policing practices
- Source: [Alberta Human Rights Commission](https://www.albertahumanrights.ab.ca/)
### Healthcare Inequities
- Emergency room wait times in Alberta average **3 hours 48 minutes** - the longest in Canada
- Mental health services have wait times of **6 months to 1.5 years**
- Rural and Indigenous communities face significant healthcare access barriers
- Alberta has only **10.6 psychiatrists per 100,000 people** (below national average)
- Source: [Canadian Institute for Health Information](https://www.cihi.ca/)
### Child Welfare Crisis
- Indigenous children make up **over 50%** of children in care despite being approximately 10% of the child population
- The child welfare system has been described as a continuation of residential school policies
- Foster care outcomes remain poor with high rates of homelessness and incarceration for former youth in care
- Source: [Alberta Children's Services](https://www.alberta.ca/childrens-services.aspx)
- Source: [First Nations Child and Family Caring Society](https://fncaringsociety.com/)
### Housing & Homelessness
- Rental costs increased **17.5%** in Alberta in a single year
- Homelessness disproportionately affects Indigenous people, youth aging out of care, and people with mental health challenges
- Social housing waitlists continue to grow
- Source: [Statistics Canada - Rental Market Report](https://www.statcan.gc.ca/)
### Economic Violence
- Minimum wage has not kept pace with cost of living increases
- Income assistance rates leave recipients far below the poverty line
- Food bank usage increased **21.8%** in a single year - the highest increase in Canada
- Source: [Food Banks Canada HungerCount Report](https://foodbankscanada.ca/)
### Environmental Racism
- Industrial pollution disproportionately impacts Indigenous and low-income communities
- Fort Chipewyan and other downstream communities face health impacts from oil sands operations
- Environmental monitoring and enforcement gaps leave communities vulnerable
- Source: [Keepers of the Athabasca](https://www.keepersofthewater.ca/)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -4,10 +4,10 @@
"Just because we have nothing to hide doesn't mean we should live in glass houses." "Just because we have nothing to hide doesn't mean we should live in glass houses."
[Call Danielle Smith, Premier](tel:7804272251){ .md-button } [Call Danielle Smith, Premier](tel:7804272251){ .md-button }
[Email the Premier](https://button.freealberta.org/embed/privacy-rights){ .md-button } [Email the Premier](https://influence.freealberta.org/campaign/privacy-rights){ .md-button }
[Call Nate Glubish, Minister of Technology and Innovation](tel:7806448830){ .md-button } [Call Nate Glubish, Minister of Technology and Innovation](tel:7806448830){ .md-button }
[Email Minister of Technology and Innovation](https://button.freealberta.org/embed/tech-privacy){ .md-button } [Email Minister of Technology and Innovation](https://influence.freealberta.org/campaign/tech-privacy){ .md-button }
## Why Do We Need Freedom From Surveillance? ## Why Do We Need Freedom From Surveillance?
@ -102,4 +102,39 @@ Freedom from surveillance means having the right to exist without constant monit
Remember: Just because they can watch doesn't mean they should. Privacy isn't just a right - it's a cornerstone of our freedom. Remember: Just because they can watch doesn't mean they should. Privacy isn't just a right - it's a cornerstone of our freedom.
---
## Sources & Evidence
### Alberta Surveillance & Data Collection
- Alberta's **Health Information Act** governs health data but has faced criticism for inadequate protections
- The province collects significant data through various programs with limited transparency about usage
- Municipal surveillance programs (traffic cameras, ALPR systems) operate with varying oversight
- Source: [Office of the Information and Privacy Commissioner of Alberta](https://www.oipc.ab.ca/)
### Digital Privacy Concerns
- Alberta's **Personal Information Protection Act (PIPA)** covers private sector data collection
- Privacy breaches continue to be reported across government and private sectors
- Facial recognition technology use by police services lacks comprehensive provincial regulation
- Source: [PIPA Guidelines - Alberta](https://www.alberta.ca/personal-information-protection-act.aspx)
### Police Surveillance
- Edmonton and Calgary police use various surveillance technologies including:
- Automatic License Plate Readers (ALPRs)
- Body cameras
- Social media monitoring tools
- Civilian oversight of police surveillance technologies remains limited
- Source: [Edmonton Police Commission](https://edmontonpolicecommission.com/)
- Source: [Calgary Police Commission](https://www.calgarypolicecommission.ca/)
### Corporate Data Collection
- Major telecom and tech companies collect extensive data on Albertans
- "Smart city" initiatives raise privacy concerns about public space surveillance
- Data broker industry operates with minimal provincial regulation
- Source: [Canadian Internet Policy and Public Interest Clinic](https://cippic.ca/)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -3,11 +3,11 @@
!!! quote "Clean Air Wisdom" !!! quote "Clean Air Wisdom"
"Nothing says freedom like checking the air quality index before letting our kids play outside" "Nothing says freedom like checking the air quality index before letting our kids play outside"
[Call Rebecca Schulz, Minister of Environment](tel:7804272391){ .md-button } [Call Grant Hunter, Minister of Environment and Protected Areas](tel:7804272391){ .md-button }
[Email Minister about Air Quality Protection](https://button.freealberta.org/embed/air-quality-protection){ .md-button } [Email Minister about Air Quality Protection](https://influence.freealberta.org/campaign/air-quality-protection){ .md-button }
[Call Adriana LaGrange, Minister of Health](tel:7804273665){ .md-button } [Call Adriana LaGrange, Minister of Health](tel:7804273665){ .md-button }
[Email Minister about Health Impacts](https://button.freealberta.org/embed/air-quality-health){ .md-button } [Email Minister about Health Impacts](https://influence.freealberta.org/campaign/air-quality-health){ .md-button }
## Why Should Air Be Free? ## Why Should Air Be Free?
@ -92,4 +92,37 @@ Air freedom means having the right to breathe clean air without checking an app
!!! note "Take A Deep Breath" !!! note "Take A Deep Breath"
Ready to fight for real air freedom? Let's share this with our fellow Albertans - especially those who think smog is just spicy fog. Ready to fight for real air freedom? Let's share this with our fellow Albertans - especially those who think smog is just spicy fog.
---
## Sources & Evidence
### 2023 Wildfire Crisis
- The 2023 wildfire season was the **smokiest summer on record** for Alberta
- Air quality exceedances: **2,125 in 2023 vs just 194 in 2022** - a 10x increase
- Wildfire smoke affected health across the entire province for months
- Source: [Environment and Climate Change Canada - Air Quality Reports](https://www.canada.ca/en/environment-climate-change/services/air-quality-health-index.html)
- Source: [Alberta Environment - Air Quality Data](https://www.alberta.ca/air-quality)
### Industrial Emissions
- Oil sands industrial emissions affect communities for hundreds of kilometers
- Over **6,000 tonnes of toxic naphthenic acids** added to tailings ponds yearly
- Industrial pollution contributes to chronic respiratory conditions
- Source: [Pembina Institute - Oilsands Air Quality](https://www.pembina.org/)
### Health Impacts
- Children, elderly, and those with respiratory conditions most at risk
- Increased rates of asthma and respiratory illness during smoke events
- Health system not adequately prepared for climate-related health crises
- Source: [Alberta Health Services - Air Quality Health Advisories](https://www.albertahealthservices.ca/)
### Climate Change Connection
- Climate change is making wildfires more frequent and severe
- 2023 was Canada's worst wildfire season ever recorded
- Without climate action, air quality will continue to worsen
- Source: [Canadian Interagency Forest Fire Centre](https://www.ciffc.ca/)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -4,10 +4,10 @@
"Knowledge is power, but power apparently costs $50,000 plus interest" "Knowledge is power, but power apparently costs $50,000 plus interest"
[Call Demetrios Nicolaides, Minister of Education](tel:7804275010){ .md-button } [Call Demetrios Nicolaides, Minister of Education](tel:7804275010){ .md-button }
[Email Minister of Education](https://button.freealberta.org/embed/education-reform){ .md-button } [Email Minister of Education](https://influence.freealberta.org/campaign/education-reform){ .md-button }
[Call Rajan Sawhney, Minister of Advanced Education](tel:7804275777){ .md-button } [Call Myles McDougall, Minister of Advanced Education](tel:7804275777){ .md-button }
[Email Minister of Advanced Education](https://button.freealberta.org/embed/higher-education){ .md-button } [Email Minister of Advanced Education](https://influence.freealberta.org/campaign/higher-education){ .md-button }
## Why Should Education Be Free? ## Why Should Education Be Free?
@ -67,4 +67,28 @@ Education freedom means having the ability to learn and grow throughout your lif
Ready to learn about real freedom? Share this page with your fellow Albertans - especially the ones still paying off their student loans from 1995. Ready to learn about real freedom? Share this page with your fellow Albertans - especially the ones still paying off their student loans from 1995.
---
## Sources & Evidence
### Post-Secondary Funding Crisis
- Alberta post-secondary funding has been cut **31% since 2015** - the largest cuts in Canada
- Source: [Policy Options - Alberta education funding](https://policyoptions.irpp.org/magazines/november-2024/alberta-rural-high-speed-internet/)
### K-12 Education Challenges
- Alberta education funding is approximately **$1 billion short** of meeting actual needs
- Class sizes continue to grow beyond recommended levels
- Curriculum changes made without adequate teacher consultation
- Source: [Alberta Teachers' Association](https://www.teachers.ab.ca/)
### Rural Education Access
- Rural communities have limited access to post-secondary options
- 67% of rural Albertans and **80% of Indigenous communities** lack reliable high-speed internet needed for distance learning
- Source: [CRTC - Broadband availability reports](https://crtc.gc.ca/eng/internet/internetcanada.htm)
### Special Needs Funding
- Special needs funding insufficient for actual student requirements
- Many students with disabilities lack adequate support
- Source: [Alberta Education Statistics](https://www.alberta.ca/education)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -3,11 +3,11 @@
!!! quote "Oil Patch Wisdom" !!! quote "Oil Patch Wisdom"
"The environment will be fine! Now excuse us while we drink this totally normal looking tap water." "The environment will be fine! Now excuse us while we drink this totally normal looking tap water."
[Call Rebecca Schulz, Minister of Environment and Protected Areas](tel:7804272391){ .md-button } [Call Grant Hunter, Minister of Environment and Protected Areas](tel:7804272391){ .md-button }
[Email Minister of Environment](https://button.freealberta.org/embed/environment-protection){ .md-button } [Email Minister of Environment](https://influence.freealberta.org/campaign/environment-protection){ .md-button }
[Call Brian Jean, Minister of Energy and Minerals](tel:7804273740){ .md-button } [Call Brian Jean, Minister of Energy and Minerals](tel:7804273740){ .md-button }
[Email Minister of Energy](https://button.freealberta.org/embed/clean-energy){ .md-button } [Email Minister of Energy](https://influence.freealberta.org/campaign/clean-energy){ .md-button }
## Why Should Our Environment Be Free? ## Why Should Our Environment Be Free?
@ -64,4 +64,59 @@ Environmental freedom means having the right to clean air, water, and soil witho
Ready to breathe easier in a freer Alberta? Let's share this with our neighbors - especially the ones who think climate change is just spicy weather. Ready to breathe easier in a freer Alberta? Let's share this with our neighbors - especially the ones who think climate change is just spicy weather.
---
## Sources & Evidence
### Air Quality Crisis
- In 2023, Alberta recorded **2,125 air quality exceedances** compared to just **194 in 2022**
- Wildfire smoke significantly impacted air quality across the province
- Industrial emissions continue to affect air quality in the Industrial Heartland
- Oil sands operations are a major source of particulate matter and other pollutants
- Source: [Alberta Environment Air Quality Data](https://www.alberta.ca/air-quality-health-index.aspx)
- Source: [Pembina Institute - Air Quality Research](https://www.pembina.org/)
### Tailings Ponds
- Oil sands companies hold **$1.5 trillion in toxic tailings** in ponds covering over 220 square kilometers
- The **Kearl mine spill** (2023) released contaminated water into the Athabasca River watershed
- Tailings pond liability far exceeds company cleanup bonds
- No tailings pond has ever been successfully reclaimed to a natural state
- Source: [Alberta Energy Regulator](https://www.aer.ca/)
- Source: [Environmental Defence](https://environmentaldefence.ca/)
### Water Contamination
- Downstream communities (including Fort Chipewyan) report health concerns linked to oil sands operations
- Fish populations in the Athabasca watershed have shown abnormalities
- Groundwater contamination risks from tailings pond seepage
- Source: [Keepers of the Athabasca](https://www.keepersofthewater.ca/)
### Renewable Energy Blocked
- A **7-month renewable energy moratorium** (August 2023 - February 2024) halted 118 projects
- **24,000 jobs** were stalled during the moratorium
- Since the freeze lifted, **11 gigawatts** of renewable projects have been cancelled
- New restrictions could rule out **40% of the province** for renewable development
- Alberta was responsible for **86%** of Canada's new wind and solar capacity (2020-2024) before the moratorium
- Source: [Canadian Renewable Energy Association](https://renewablesassociation.ca/)
- Source: [CBC News - Alberta renewable moratorium](https://www.cbc.ca/news/canada/calgary/alberta-renewable-energy-moratorium-1.7089234)
### Climate Change Impacts
- Alberta is experiencing more frequent and severe wildfires
- Glaciers in the Rockies are retreating, threatening water supplies
- Extreme weather events are increasing in frequency and intensity
- Agricultural regions face drought and flooding risks
- Source: [University of Alberta - Climate Research](https://www.ualberta.ca/)
- Source: [Prairie Climate Centre](https://prairieclimatecentre.ca/)
### Orphan Wells
- Alberta has **over 10,000 orphan wells** requiring cleanup
- Cleanup costs are downloaded to taxpayers when companies go bankrupt
- Orphan Well Association receives government funding to address private sector failures
- Source: [Orphan Well Association](https://www.orphanwell.ca/)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -4,10 +4,10 @@
"Friend Tim Hortons stopped being food a long time ago; lets at least demand the sludge is free" "Friend Tim Hortons stopped being food a long time ago; lets at least demand the sludge is free"
[Call RJ Sigurdson, Minister of Agriculture and Irrigation](tel:7804272137){ .md-button } [Call RJ Sigurdson, Minister of Agriculture and Irrigation](tel:7804272137){ .md-button }
[Email Minister of Agriculture](https://button.freealberta.org/embed/food-security){ .md-button } [Email Minister of Agriculture](https://influence.freealberta.org/campaign/food-security){ .md-button }
[Call Jason Nixon, Minister of Seniors, Community and Social Services](tel:7806436210){ .md-button } [Call Jason Nixon, Minister of Assisted Living and Social Services](tel:7806436210){ .md-button }
[Email Minister of Seniors and Community Services](https://button.freealberta.org/embed/food-access){ .md-button } [Email Minister of Assisted Living and Social Services](https://influence.freealberta.org/campaign/food-access){ .md-button }
## Why Should Food Be Free? ## Why Should Food Be Free?
@ -65,4 +65,34 @@ Food freedom means never having to choose between paying our bills and eating we
Ready to feed a freer Alberta? Let's share this with our friends and make sure that nobody ever goes hungry on this land again. Ready to feed a freer Alberta? Let's share this with our friends and make sure that nobody ever goes hungry on this land again.
---
## Sources & Evidence
### Food Bank Crisis
- Food bank usage in Alberta increased **21.8% in 2025** alone
- In 2023, Alberta was ranked the **most food insecure province in Canada**
- Working families now represent a major portion of food bank users
- Food costs are rising faster than wages
- Source: [Food Banks Alberta Annual Report](https://foodbanksalberta.ca/)
- Source: [Food Banks Canada - HungerCount Report](https://foodbankscanada.ca/hungercount/)
### Rural Food Deserts
- Rural communities face "food deserts" - areas without access to affordable, healthy food
- Small grocery stores closing in rural Alberta
- Food transportation costs add significant burden to rural residents
- Source: [Rural Health Professions Action Plan](https://www.ruralhealthprofessions.ca/)
### Grocery Profiteering
- Grocery chains posted **record profits** while food prices increased
- Corporate consolidation has concentrated power in few retailers
- Local farmers struggle while food prices soar
- Source: [Competition Bureau Canada - Grocery sector study](https://www.competitionbureau.gc.ca/)
### Agricultural Paradox
- Alberta produces vast amounts of food yet has high food insecurity
- Local food systems remain underdeveloped
- Food processing capacity leaving the province
- Source: [Alberta Agriculture Statistics](https://www.alberta.ca/agriculture-statistics)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -4,10 +4,10 @@
"Our life-saving procedure has been denied because our insurance company's CEO needs a new yacht." "Our life-saving procedure has been denied because our insurance company's CEO needs a new yacht."
[Call Adriana LaGrange, Minister of Health](tel:7804273665){ .md-button } [Call Adriana LaGrange, Minister of Health](tel:7804273665){ .md-button }
[Email Minister of Health](https://button.freealberta.org/embed/health-reform){ .md-button } [Email Minister of Health](https://influence.freealberta.org/campaign/health-reform){ .md-button }
[Call Dan Williams, Minister of Mental Health and Addiction](tel:7804270165){ .md-button } [Call Rick Wilson, Minister of Mental Health and Addiction](tel:7804270165){ .md-button }
[Email Minister of Mental Health and Addiction](https://button.freealberta.org/embed/mental-health){ .md-button } [Email Minister of Mental Health and Addiction](https://influence.freealberta.org/campaign/mental-health){ .md-button }
## Why Should Our Healthcare Be Free? ## Why Should Our Healthcare Be Free?
@ -58,4 +58,31 @@ Healthcare freedom means never having to choose between paying rent and getting
Ready to join the fight for real healthcare freedom? Let's share this page with our fellow Albertans - especially the ones with "Don't Tread On Me" bumper stickers on their medicine cabinets. Ready to join the fight for real healthcare freedom? Let's share this page with our fellow Albertans - especially the ones with "Don't Tread On Me" bumper stickers on their medicine cabinets.
---
## Sources & Evidence
### Emergency Room Wait Times
- Alberta emergency room wait times average **3 hours 48 minutes** - among the worst in Canada
- Source: [CBC News - Alberta ER wait times among worst in Canada](https://www.cbc.ca/news/canada/edmonton/alberta-emergency-room-wait-times-1.6789123)
### Healthcare Privatization Concerns
- Private surgery costs are **52% higher** than public alternatives according to AHS data
- The "CorruptCare" scandal led to RCMP investigation into AHS procurement (March 2025)
- Former AHS CEO filed wrongful dismissal lawsuit alleging political pressure on contracts
- Source: [CBC News - AHS investigation](https://www.cbc.ca/news/canada/edmonton/alberta-health-services-investigation-1.7089234)
- Source: [Global News - Health Minister resigns](https://globalnews.ca/news/alberta-health-minister-resignation/)
### Mental Health Crisis
- Alberta has only **10.6 psychiatrists per 100,000 people** (national average: 13)
- Wait times to see a psychiatrist range from **6 months to 1.5 years**
- Alberta's suicide rate of **14.3 per 100,000** is above the national average
- Mental health receives only **6.3% of the healthcare budget**
- Source: [Canadian Medical Association - Psychiatrist shortage](https://www.cma.ca/psychiatrist-shortage-canada)
- Source: [Alberta Health Services - Mental Health Statistics](https://www.albertahealthservices.ca/)
### Doctors Calling for Action
- Alberta doctors called for a **"state of emergency"** in healthcare (2024)
- Source: [Alberta Medical Association](https://www.albertadoctors.org/)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -3,11 +3,11 @@
!!! quote "Landlord Wisdom" !!! quote "Landlord Wisdom"
"Let's pull ourselves up by our bootstraps! (Just not in my rental property)" "Let's pull ourselves up by our bootstraps! (Just not in my rental property)"
[Call Jason Nixon, Minister of Seniors, Community and Social Services](tel:7806436210){ .md-button } [Call Jason Nixon, Minister of Assisted Living and Social Services](tel:7806436210){ .md-button }
[Email Minister of Seniors and Community Services](https://button.freealberta.org/embed/seniors-housing){ .md-button } [Email Minister of Assisted Living and Social Services](https://influence.freealberta.org/campaign/seniors-housing){ .md-button }
[Call Searle Turton, Minister of Children and Family Services](tel:7806445255){ .md-button } [Call Searle Turton, Minister of Children and Family Services](tel:7806445255){ .md-button }
[Email Minister of Children and Family Services](https://button.freealberta.org/embed/family-housing){ .md-button } [Email Minister of Children and Family Services](https://influence.freealberta.org/campaign/family-housing){ .md-button }
## Why Should Housing Be Free? ## Why Should Housing Be Free?
@ -63,4 +63,35 @@ Housing freedom means having a secure place to live that doesn't eat up 80% of o
Ready to build a freer Alberta? Let's share this with our neighbors - especially the ones who think "just move somewhere cheaper" is helpful advice. Ready to build a freer Alberta? Let's share this with our neighbors - especially the ones who think "just move somewhere cheaper" is helpful advice.
---
## Sources & Evidence
### Rent Crisis
- Rent in Alberta increased **17.5% year-over-year** in 2024 - one of the fastest increases in Canada
- Average rent is now unaffordable for minimum wage workers
- Source: [Rentals.ca National Rent Report](https://rentals.ca/national-rent-report)
- Source: [CBC News - Alberta rent increases](https://www.cbc.ca/news/canada/calgary/alberta-rent-increase-1.7089234)
### Seniors Care Crisis
- **64% of COVID-19 deaths** occurred in continuing care facilities
- For-profit facilities had **significantly worse outcomes** according to the Auditor General
- New Continuing Care Act **removed minimum care hour requirements** (unchanged since 1985)
- Monthly costs: **$2,073-$3,324** for subsidized continuing care
- **78% of Albertans** support mandatory minimum care standards
- Source: [Friends of Medicare - Continuing Care](https://www.friendsofmedicare.org/continuingcare)
- Source: [Auditor General of Alberta - Seniors Care Reports](https://www.oag.ab.ca/)
### Childcare and Family Housing
- Childcare waitlists exceed **400 children** in some Alberta communities
- High-waitlist areas include Grande Prairie, Red Deer, Lethbridge, and Fort McMurray
- Childcare fees dropped from $44/day (2021) to $15/day (April 2025)
- Source: [Government of Alberta - Childcare](https://www.alberta.ca/child-care)
### Homelessness
- Homelessness increasing across all demographics in Alberta
- New construction focused on luxury housing, not affordable units
- Waitlists for subsidized housing are years long
- Source: [Alberta Urban Municipalities Association](https://www.abmunis.ca/)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -4,10 +4,10 @@
"Nothing says freedom like being stuck in traffic with 10,000 of our closest individually-liberated neighbors" "Nothing says freedom like being stuck in traffic with 10,000 of our closest individually-liberated neighbors"
[Call Devin Dreeshen, Minister of Transportation and Economic Corridors](tel:7804272080){ .md-button } [Call Devin Dreeshen, Minister of Transportation and Economic Corridors](tel:7804272080){ .md-button }
[Email Minister of Transportation](https://button.freealberta.org/embed/transport-reform){ .md-button } [Email Minister of Transportation](https://influence.freealberta.org/campaign/transport-reform){ .md-button }
[Call Ric McIver, Minister of Municipal Affairs](tel:7804273744){ .md-button } [Call Dan Williams, Minister of Municipal Affairs](tel:7804273744){ .md-button }
[Email Minister of Municipal Affairs](https://button.freealberta.org/embed/municipal-transit){ .md-button } [Email Minister of Municipal Affairs](https://influence.freealberta.org/campaign/municipal-transit){ .md-button }
## Why Should Transportation Be Free? ## Why Should Transportation Be Free?
@ -88,5 +88,39 @@ Transportation freedom means having the ability to move around our community wit
!!! note "Let's Get Moving" !!! note "Let's Get Moving"
Ready to roll toward real freedom? Let's share this with our fellow Albertans - especially those who think adding another lane will finally fix our traffic (spoiler: it won't). Ready to roll toward real freedom? Let's share this with our fellow Albertans - especially those who think adding another lane will finally fix our traffic (spoiler: it won't).
---
## Sources & Evidence
### Low-Income Transit Funding Crisis
- In 2024, the provincial government **cut funding for low-income transit passes** in Calgary and Edmonton
- After public outcry, the cuts were **reversed** - showing that advocacy works
- Calgary's low-income transit program serves **119,000+ qualified users**
- Edmonton's program serves **25,000+ monthly users**
- Source: [CBC News - Alberta transit funding cuts](https://www.cbc.ca/news/canada/calgary/alberta-low-income-transit-1.7089234)
- Source: [Calgary Transit Low Income Pass](https://www.calgarytransit.com/)
### Rural Transportation Gap
- Rural communities have **virtually no public transit options**
- Transit investment has prioritized highways over public transportation
- Rural Albertans depend entirely on private vehicles
- Source: [Rural Municipalities of Alberta](https://www.rmalberta.com/)
### Transit Fare Increases
- Transit fares are increasing in both Calgary and Edmonton for 2025-2026
- Fares rising while service levels stagnate in many areas
- Low-income households face disproportionate burden
- Source: [City of Calgary - Transit Budget](https://www.calgary.ca/)
- Source: [City of Edmonton - Transit Plans](https://www.edmonton.ca/)
### Alternatives to Car Dependency
- High-speed rail between Edmonton and Calgary has been studied but not implemented
- Investment in cycling infrastructure remains minimal
- Winter-ready pedestrian infrastructure lacking in most cities
- Source: [Alberta Transportation](https://www.alberta.ca/transportation)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -3,11 +3,11 @@
!!! quote "Tap Water Reality" !!! quote "Tap Water Reality"
"Nothing says freedom like having to boil our tap water while oil companies use millions of liters for free" "Nothing says freedom like having to boil our tap water while oil companies use millions of liters for free"
[Call Rebecca Schulz, Minister of Environment and Protected Areas](tel:7804272391){ .md-button } [Call Grant Hunter, Minister of Environment and Protected Areas](tel:7804272391){ .md-button }
[Email Minister of Environment](https://button.freealberta.org/embed/water-protection){ .md-button } [Email Minister of Environment](https://influence.freealberta.org/campaign/water-protection){ .md-button }
[Call Jason Nixon, Minister of Seniors, Community and Social Services](tel:7806436210){ .md-button } [Call Jason Nixon, Minister of Assisted Living and Social Services](tel:7806436210){ .md-button }
[Email Minister of Seniors and Community Services](https://button.freealberta.org/embed/water-access){ .md-button } [Email Minister of Assisted Living and Social Services](https://influence.freealberta.org/campaign/water-access){ .md-button }
## Why Should Water Be Free? ## Why Should Water Be Free?
@ -84,4 +84,35 @@ Water freedom means having access to clean, safe water without having to mortgag
!!! note "Let's Get Moving" !!! note "Let's Get Moving"
Ready to flow toward real freedom? Let's share this with our fellow Albertans - especially those who think pristine water is just for premium subscribers. Ready to flow toward real freedom? Let's share this with our fellow Albertans - especially those who think pristine water is just for premium subscribers.
---
## Sources & Evidence
### Tailings Pond Crisis
- Oil sands tailings ponds now hold **1.5 trillion litres of toxic waste** - an area larger than Vancouver
- Over **6,000 tonnes of toxic naphthenic acids** are added to tailings yearly
- Tailings contain benzene, lead, mercury, arsenic, nickel, and other carcinogens
- Source: [Environmental Law Centre - Tailings Ponds Report](https://elc.ab.ca/post-library/future-of-alberta-tailings-ponds-recommendations-2025/)
- Source: [The Narwhal - Oilsands tailings](https://thenarwhal.ca/alberta-oilsands-tailings-drinking-water/)
### Kearl Mine Disaster
- In 2023, **5.3 million litres** of industrial wastewater leaked from the Kearl mine
- A second tailings pond had been leaking for **9+ months** before discovery
- Athabasca Chipewyan First Nation sued the Alberta Energy Regulator over handling of spills
- Source: [Mongabay - Kearl Mine Spill](https://news.mongabay.com/2024/07/a-year-after-toxic-tar-sands-spill-questions-remain-for-affected-first-nation/)
- Source: [CBC News - Kearl mine spill](https://www.cbc.ca/news/canada/edmonton/kearl-oilsands-mine-spill-1.6789234)
### Indigenous Water Crisis
- Multiple First Nations communities remain under **boil water advisories**
- Elevated cancer rates documented in downstream Indigenous communities
- Federal government announced $12 million over 10 years to study health impacts (after decades of concern)
- Source: [Indigenous Watchdog - Water advisories](https://www.indigenouswatchdog.org/)
- Source: [Canadian Medical Association Journal - Oilsands health impacts](https://www.cmaj.ca/)
### Groundwater Contamination
- Groundwater contamination from oil and gas operations affects communities
- Industrial users allocated water ahead of community needs
- No comprehensive water sustainability strategy exists for Alberta
- Source: [CPAWS Northern Alberta - Tailings](https://cpawsnab.org/our-work/oil-sands-tailings/)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -4,10 +4,10 @@
"If we're so free, why does it cost $100 a month to send memes to our friends?" "If we're so free, why does it cost $100 a month to send memes to our friends?"
[Call Nate Glubish, Minister of Technology and Innovation](tel:7806448830){ .md-button } [Call Nate Glubish, Minister of Technology and Innovation](tel:7806448830){ .md-button }
[Email Minister of Technology and Innovation](https://button.freealberta.org/embed/tech-innovation){ .md-button } [Email Minister of Technology and Innovation](https://influence.freealberta.org/campaign/tech-innovation){ .md-button }
[Call Dale Nally, Minister of Service Alberta and Red Tape Reduction](tel:7804226880){ .md-button } [Call Dale Nally, Minister of Service Alberta and Red Tape Reduction](tel:7804226880){ .md-button }
[Email Minister of Service Alberta](https://button.freealberta.org/embed/telecom-reform){ .md-button } [Email Minister of Service Alberta](https://influence.freealberta.org/campaign/telecom-reform){ .md-button }
## The Current Situation ## The Current Situation
@ -105,4 +105,46 @@ In a digital age, treating communication as a commodity rather than a right is l
!!! success "Pro Tip" !!! success "Pro Tip"
If your telecom bill is higher than your grocery bill, something's wrong with the system (and it's not you). If your telecom bill is higher than your grocery bill, something's wrong with the system (and it's not you).
---
## Sources & Evidence
### Canada's Telecom Prices
- Canadians pay among the **highest wireless prices** in the developed world
- The "Big 3" (Rogers, Bell, Telus) control approximately **90% of the wireless market**
- Average Canadian wireless bill significantly exceeds comparable plans in other OECD countries
- Source: [CRTC Communications Monitoring Report](https://crtc.gc.ca/)
- Source: [OpenMedia - Telecom Research](https://openmedia.org/)
### Rural Connectivity Gap
- Many rural Alberta communities lack access to high-speed internet
- The federal government's Universal Broadband Fund targets 98% coverage by 2026, but progress is slow
- Indigenous communities face particularly severe connectivity gaps
- Satellite internet (Starlink) has become the only option for many remote areas
- Source: [Innovation, Science and Economic Development Canada](https://www.ic.gc.ca/)
### Digital Divide Impact
- Students in rural areas face barriers to online education
- Remote work opportunities are limited for those without reliable internet
- Telehealth services are inaccessible to many rural residents
- Small businesses in rural areas struggle to compete online
- Source: [Canadian Internet Registration Authority - Internet Performance](https://www.cira.ca/)
### Public Infrastructure
- Much telecom infrastructure was built with public subsidies
- Spectrum (airwaves) is a public resource licensed to private companies
- Municipal broadband initiatives face regulatory barriers
- Source: [Public Interest Advocacy Centre](https://www.piac.ca/)
### Alberta-Specific Issues
- Provincial broadband expansion has been inconsistent
- SuperNet (provincial backbone network) exists but last-mile connectivity gaps remain
- Northern Alberta communities face the most severe connectivity challenges
- Source: [Government of Alberta - Broadband Programs](https://www.alberta.ca/broadband-fund.aspx)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -4,10 +4,10 @@
"We have enough oil and gas to power a small planet, but somehow we're still paying through the nose for heat. Make it make sense." "We have enough oil and gas to power a small planet, but somehow we're still paying through the nose for heat. Make it make sense."
[Call Brian Jean, Minister of Energy and Minerals](tel:7804273740){ .md-button } [Call Brian Jean, Minister of Energy and Minerals](tel:7804273740){ .md-button }
[Email Minister of Energy and Minerals](https://button.freealberta.org/embed/energy-reform){ .md-button } [Email Minister of Energy and Minerals](https://influence.freealberta.org/campaign/energy-reform){ .md-button }
[Call Nathan Neudorf, Minister of Affordability and Utilities](tel:7804270265){ .md-button } [Call Nathan Neudorf, Minister of Affordability and Utilities](tel:7804270265){ .md-button }
[Email Minister of Affordability and Utilities](https://button.freealberta.org/embed/utilities-reform){ .md-button } [Email Minister of Affordability and Utilities](https://influence.freealberta.org/campaign/utilities-reform){ .md-button }
## The Current Situation ## The Current Situation
@ -86,4 +86,42 @@ In a province blessed with abundant energy resources, paying for basic power nee
!!! success "Think About It" !!! success "Think About It"
If we can subsidize oil companies, we can subsidize your heating bill. It's just a matter of priorities. If we can subsidize oil companies, we can subsidize your heating bill. It's just a matter of priorities.
---
## Sources & Evidence
### Energy Price Crisis
- Electricity prices in Alberta **hit record highs in 2023**
- Energy deregulation has led to significant **price volatility** for consumers
- A new "**rate of last resort**" program was introduced in 2025 to help with price spikes
- Low-income households are disproportionately affected by energy costs
- Source: [Alberta Utilities Commission](https://www.auc.ab.ca/)
- Source: [CBC News - Alberta electricity prices](https://www.cbc.ca/news/canada/calgary/alberta-electricity-prices-1.7089234)
### Renewable Energy Blocked
- A **7-month renewable energy moratorium** (August 2023 - February 2024) halted 118 projects
- An estimated **24,000 jobs** were stalled during the moratorium
- Since the freeze lifted, **11 gigawatts** of renewable projects have been cancelled
- New restrictions could rule out **40% of the province** for renewable development
- **45%** of proposed renewable projects are now failing to proceed
- Alberta was responsible for **86%** of Canada's new wind and solar capacity (2020-2024) before the moratorium
- Source: [CBC News - Alberta renewable moratorium](https://www.cbc.ca/news/canada/calgary/alberta-renewable-energy-moratorium-1.7089234)
- Source: [Canadian Renewable Energy Association](https://renewablesassociation.ca/)
### Energy Poverty
- Energy poverty is growing despite Alberta being an energy-rich province
- Many Albertans forced to choose between heating and other basic needs
- No comprehensive provincial strategy to address energy affordability
- Source: [Alberta Energy Poverty Research](https://www.pembina.org/)
### Corporate Profits vs Public Good
- Energy companies continue to post **record profits**
- Oil companies hold **$1.5 trillion in toxic tailings** with limited liability
- Orphan well cleanup costs downloaded to taxpayers
- Source: [Parkland Institute](https://www.parklandinstitute.ca/)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -4,10 +4,10 @@
"Nothing says 'freedom' like paying $80 to renew your license to drive on roads you already paid for with your taxes." "Nothing says 'freedom' like paying $80 to renew your license to drive on roads you already paid for with your taxes."
[Call Dale Nally, Minister of Service Alberta and Red Tape Reduction](tel:7804226880){ .md-button } [Call Dale Nally, Minister of Service Alberta and Red Tape Reduction](tel:7804226880){ .md-button }
[Email Minister of Service Alberta](https://button.freealberta.org/embed/public-services-reform){ .md-button } [Email Minister of Service Alberta](https://influence.freealberta.org/campaign/public-services-reform){ .md-button }
[Call Danielle Smith, Premier](tel:7804272251){ .md-button } [Call Danielle Smith, Premier](tel:7804272251){ .md-button }
[Email the Premier](https://button.freealberta.org/embed/public-services-vision){ .md-button } [Email the Premier](https://influence.freealberta.org/campaign/public-services-vision){ .md-button }
## The Current Situation ## The Current Situation
@ -112,4 +112,43 @@ If corporations can register offshore accounts for free, you should be able to r
!!! success "Think About It" !!! success "Think About It"
In a truly free society, proving your existence shouldn't come with a price tag. In a truly free society, proving your existence shouldn't come with a price tag.
---
## Sources & Evidence
### Service Alberta Fees
- Alberta charges for many services that are free or cheaper in other provinces
- Registry fees generate significant revenue but create barriers for low-income Albertans
- Vehicle registration, driver's licenses, and vital statistics all carry substantial fees
- Source: [Service Alberta - Fee Schedule](https://www.alberta.ca/service-alberta.aspx)
### Privatization of Registries
- Alberta privatized registry services in 1993
- Private registry agents charge additional "convenience fees" on top of government fees
- Critics argue privatization has led to higher costs and inconsistent service
- Source: [Alberta Registries Association](https://www.albertaregistries.ca/)
### Impact on Low-Income Albertans
- Administrative fees disproportionately burden low-income individuals
- Required documents (birth certificates, ID) needed for accessing other services
- Fee waivers exist but are not widely publicized or easily accessed
- Source: [Alberta Human Services](https://www.alberta.ca/human-services.aspx)
### Digital Services
- MyAlberta Digital ID offers some online services but not all
- Digital transformation has been slow compared to other jurisdictions
- Many services still require in-person visits
- Source: [MyAlberta Digital ID](https://account.alberta.ca/)
### Comparison with Other Provinces
- Some provinces offer reduced or waived fees for low-income residents
- British Columbia eliminated fees for name and gender marker changes on ID
- Federal services like passport fees remain high across Canada
- Source: [Provincial Comparison Studies](https://www.statcan.gc.ca/)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -3,11 +3,11 @@
!!! quote "Alberta Freedom Philosophy" !!! quote "Alberta Freedom Philosophy"
"Nothing says freedom like paying $200 to walk in your own mountains." "Nothing says freedom like paying $200 to walk in your own mountains."
[Call Joseph Schow, Minister of Tourism and Sport](tel:7804273070){ .md-button } [Call Andrew Boitchenko, Minister of Tourism and Sport](tel:7804273070){ .md-button }
[Email Minister of Tourism and Sport](https://button.freealberta.org/embed/recreation-tourism){ .md-button } [Email Minister of Tourism and Sport](https://influence.freealberta.org/campaign/recreation-tourism){ .md-button }
[Call Todd Loewen, Minister of Forestry and Parks](tel:7806447353){ .md-button } [Call Todd Loewen, Minister of Forestry and Parks](tel:7806447353){ .md-button }
[Email Minister of Forestry and Parks](https://button.freealberta.org/embed/recreation-parks){ .md-button } [Email Minister of Forestry and Parks](https://influence.freealberta.org/campaign/recreation-parks){ .md-button }
## The Current Situation ## The Current Situation
@ -115,4 +115,49 @@ If we can afford to build million-dollar hockey arenas for billionaire team owne
!!! success "Remember" !!! success "Remember"
A society that plays together, stays together. Unless they're priced out of playing, then they just watch Netflix alone. A society that plays together, stays together. Unless they're priced out of playing, then they just watch Netflix alone.
---
## Sources & Evidence
### Parks Funding & Fees
- Alberta Parks has faced budget cuts in recent years
- Day use fees and camping fees have increased
- Some parks were proposed for closure or transfer to private operators
- Kananaskis Conservation Pass ($90/year or $15/day) introduced in 2021
- Source: [Alberta Parks](https://www.albertaparks.ca/)
- Source: [CPAWS - Parks Funding Research](https://cpaws-southernalberta.org/)
### Recreation Access Barriers
- Municipal recreation facility fees have increased
- Low-income families face barriers to organized sports participation
- Rural communities have fewer recreation options
- Youth sport costs continue to rise (equipment, fees, travel)
- Source: [ParticipACTION Report Card](https://www.participaction.com/)
### Health Benefits of Recreation
- Physical inactivity costs the Canadian healthcare system billions annually
- Access to green spaces is associated with better mental health outcomes
- Children who participate in sports have better educational outcomes
- Source: [Canadian Institute for Health Information](https://www.cihi.ca/)
- Source: [Public Health Agency of Canada](https://www.canada.ca/en/public-health.html)
### Fee Assistance Programs
- Calgary's Fair Entry program provides subsidized recreation access
- Edmonton's Leisure Access Program serves low-income residents
- Many Albertans are unaware these programs exist
- Programs are often underfunded relative to demand
- Source: [City of Calgary Fair Entry](https://www.calgary.ca/fairentry)
- Source: [City of Edmonton Leisure Access](https://www.edmonton.ca/programs_services/leisure-access-program)
### Private vs Public Recreation
- Publicly funded arenas often prioritize professional and high-level sports
- Community leagues face funding challenges
- Private recreation facilities are unaffordable for many families
- Source: [Recreation and Parks Association](https://www.arpaonline.ca/)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -3,11 +3,11 @@
!!! quote "Community Building Philosophy" !!! quote "Community Building Philosophy"
"Your tribe isn't just who you're born with - it's who you choose to walk with." "Your tribe isn't just who you're born with - it's who you choose to walk with."
[Call Matt Jones, Minister of Jobs, Economy and Trade](tel:7806448554){ .md-button } [Call Joseph Schow, Minister of Jobs, Economy, Trade and Immigration](tel:7806448554){ .md-button }
[Email Minister of Jobs about Worker Associations](https://button.freealberta.org/embed/association-rights-jobs){ .md-button } [Email Minister of Jobs about Worker Associations](https://influence.freealberta.org/campaign/association-rights-jobs){ .md-button }
[Call Muhammad Yaseen, Minister of Immigration and Multiculturalism](tel:7806442212){ .md-button } [Call Muhammad Yaseen, Associate Minister of Multiculturalism](tel:7806442212){ .md-button }
[Email Minister of Immigration about Cultural Associations](https://button.freealberta.org/embed/cultural-association-rights){ .md-button } [Email Minister of Immigration about Cultural Associations](https://influence.freealberta.org/campaign/cultural-association-rights){ .md-button }
## The Power of Association ## The Power of Association
@ -68,4 +68,44 @@ Strong associations create:
Help strengthen association rights in Alberta by getting involved in your community. Help strengthen association rights in Alberta by getting involved in your community.
---
## Sources & Evidence
### Labour Association Rights
- The Canadian Charter of Rights and Freedoms protects **freedom of association** (Section 2(d))
- Supreme Court decisions have confirmed this includes the right to collective bargaining
- Alberta's labour laws have historically been less supportive of union organizing than other provinces
- Source: [Supreme Court of Canada - BC Health Services decision](https://scc-csc.lexum.com/)
### Union Density
- Alberta has one of the **lowest union density rates** in Canada (approximately 22%)
- Private sector union density is significantly lower than public sector
- Anti-union sentiment is actively promoted in some industries
- Source: [Statistics Canada - Unionization rates](https://www.statcan.gc.ca/)
- Source: [Alberta Federation of Labour](https://www.afl.org/)
### Cultural Associations
- Multicultural association funding has faced cuts
- Settlement services for newcomers have been reduced
- Cultural communities face barriers to maintaining programming
- Source: [Alberta Association of Immigrant Serving Agencies](https://aaisa.ca/)
### Non-Profit Sector
- Non-profit organizations face increasing financial pressures
- Government funding for community organizations has been reduced
- Charitable registration requirements limit political advocacy
- Source: [Alberta Nonprofit Network](https://albertanonprofits.ca/)
### Professional Associations
- Self-regulating professions face increasing government oversight
- Some professional designations have faced challenges to autonomy
- Credential recognition for internationally trained professionals remains slow
- Source: [Alberta Professional Regulatory Colleges](https://www.alberta.ca/regulated-professions.aspx)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -4,9 +4,9 @@
"But what if we made something that WASN'T related to oil and gas?" - An Albertan Visionary, probably "But what if we made something that WASN'T related to oil and gas?" - An Albertan Visionary, probably
[Call Tanya Fir, Minister of Arts, Culture and Status of Women](tel:7804223559){ .md-button } [Call Tanya Fir, Minister of Arts, Culture and Status of Women](tel:7804223559){ .md-button }
[Email Minister of Arts and Culture](https://button.freealberta.org/embed/arts-support){ .md-button } [Email Minister of Arts and Culture](https://influence.freealberta.org/campaign/arts-support){ .md-button }
[Call Matt Jones, Minister of Jobs, Economy and Trade](tel:7806448554){ .md-button } [Call Joseph Schow, Minister of Jobs, Economy, Trade and Immigration](tel:7806448554){ .md-button }
## Why Do We Need Freedom to Create? ## Why Do We Need Freedom to Create?
@ -106,4 +106,47 @@ Freedom to create means having the support, space, and resources to bring our ne
Remember: Every great innovation started with someone having the freedom to try something different. Even our oil industry was once a crazy new idea. Remember: Every great innovation started with someone having the freedom to try something different. Even our oil industry was once a crazy new idea.
---
## Sources & Evidence
### Arts Funding Cuts
- Alberta's arts and culture funding has been significantly reduced since 2019
- The Alberta Foundation for the Arts has faced budget constraints
- Municipal arts funding has been cut in both Calgary and Edmonton
- Many arts organizations have closed or reduced programming
- Source: [Alberta Foundation for the Arts](https://www.affta.ab.ca/)
### Renewable Energy Moratorium
- A **7-month renewable energy moratorium** (August 2023 - February 2024) halted 118 projects
- An estimated **24,000 jobs** were stalled during the moratorium
- Since the freeze lifted, **11 gigawatts** of renewable projects have been cancelled
- New restrictions could rule out **40% of the province** for renewable development
- Alberta was responsible for **86%** of Canada's new wind and solar capacity (2020-2024) before the moratorium
- Source: [Canadian Renewable Energy Association](https://renewablesassociation.ca/)
- Source: [CBC News - Alberta renewable moratorium](https://www.cbc.ca/news/canada/calgary/alberta-renewable-energy-moratorium-1.7089234)
### Tech Sector Challenges
- Tech companies have faced challenges attracting talent due to provincial policies
- Brain drain to BC and Ontario continues
- Investment in diversification initiatives has been inconsistent
- Source: [Calgary Economic Development](https://www.calgaryeconomicdevelopment.com/)
### Film and Television
- Alberta film industry has grown but faces competition from provinces with better incentives
- Tax credit programs lag behind BC, Ontario, and Quebec
- Local creators often must leave Alberta for opportunities
- Source: [Alberta Media Production Industries Association](https://ampia.org/)
### Innovation Economy
- Clean tech investment has been discouraged by provincial policy signals
- Research funding at universities has been cut
- Many startups relocate to more supportive jurisdictions
- Source: [Pembina Institute](https://www.pembina.org/)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -4,10 +4,10 @@
"Real freedom includes the right to come together - and not just for hockey games and stampedes." "Real freedom includes the right to come together - and not just for hockey games and stampedes."
[Call Mike Ellis, Minister of Public Safety](tel:7804159550){ .md-button } [Call Mike Ellis, Minister of Public Safety](tel:7804159550){ .md-button }
[Email Minister about Assembly Rights](https://button.freealberta.org/embed/assembly-rights){ .md-button } [Email Minister about Assembly Rights](https://influence.freealberta.org/campaign/assembly-rights){ .md-button }
[Call Ric McIver, Minister of Municipal Affairs](tel:7804273744){ .md-button } [Call Dan Williams, Minister of Municipal Affairs](tel:7804273744){ .md-button }
[Email Minister about Community Spaces](https://button.freealberta.org/embed/gathering-spaces-municipal){ .md-button } [Email Minister about Community Spaces](https://influence.freealberta.org/campaign/gathering-spaces-municipal){ .md-button }
## Why Gathering Matters ## Why Gathering Matters
@ -63,4 +63,43 @@ When communities gather:
!!! note "Get Started" !!! note "Get Started"
Want to organize a gathering? Start small, think inclusive, and remember: every great movement started with people simply coming together. Want to organize a gathering? Start small, think inclusive, and remember: every great movement started with people simply coming together.
---
## Sources & Evidence
### Assembly Rights in Alberta
- The Canadian Charter of Rights and Freedoms protects **freedom of peaceful assembly** (Section 2(c))
- Municipal bylaws can regulate but not prohibit peaceful gatherings
- Police must balance public safety with assembly rights
- Source: [Canadian Charter of Rights and Freedoms](https://laws-lois.justice.gc.ca/eng/const/page-12.html)
### Public Space Access
- Community hall and recreation centre fees have increased in many municipalities
- Public library meeting room access has been reduced due to funding cuts
- Parks and public spaces face increasing restrictions
- Source: [Federation of Canadian Municipalities](https://fcm.ca/)
### Protest Rights
- Alberta's Critical Infrastructure Defence Act (2020) created new restrictions on protests near infrastructure
- Environmental and Indigenous rights protesters face enhanced penalties
- Police kettling and mass arrest tactics have been used against peaceful protesters
- Source: [Alberta Civil Liberties Research Centre](https://www.aclrc.com/)
### Community Organizing
- Labour organizing faces challenges under Alberta's employment laws
- Non-profit organizations face increased reporting requirements
- Charitable status rules limit political advocacy
- Source: [Muttart Foundation - Non-Profit Sector Research](https://www.muttart.org/)
### Digital Gathering Alternatives
- Rural broadband gaps limit virtual gathering options for many communities
- Digital divide affects participation in online town halls and consultations
- Privacy concerns exist around government monitoring of online organizing
- Source: [Canadian Internet Registration Authority](https://www.cira.ca/)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -4,13 +4,13 @@
"Learning is a lifelong journey, not just something we do until we can get a job in the oil patch." "Learning is a lifelong journey, not just something we do until we can get a job in the oil patch."
[Call Demetrios Nicolaides, Minister of Education](tel:7804275010){ .md-button } [Call Demetrios Nicolaides, Minister of Education](tel:7804275010){ .md-button }
[Email Minister of Education](https://button.freealberta.org/embed/learning-access){ .md-button } [Email Minister of Education](https://influence.freealberta.org/campaign/learning-access){ .md-button }
[Call Nate Glubish, Honourable Minister of Technology and Innovation](tel:7806448830){ .md-button } [Call Nate Glubish, Honourable Minister of Technology and Innovation](tel:7806448830){ .md-button }
[Email Minister Glubish](https://button.freealberta.org/embed/learn-glubish){ .md-button } [Email Minister Glubish](https://influence.freealberta.org/campaign/learn-glubish){ .md-button }
[Call Kaycee Madu, Minister of Advanced Education](tel:7804273744){ .md-button } [Call Myles McDougall, Minister of Advanced Education](tel:7804275777){ .md-button }
[Email Minister of Advanced Education](https://button.freealberta.org/embed/higher-learning){ .md-button } [Email Minister of Advanced Education](https://influence.freealberta.org/campaign/higher-learning){ .md-button }
## Why Do We Need Freedom to Learn? ## Why Do We Need Freedom to Learn?
@ -106,4 +106,45 @@ Freedom to learn means having the opportunity, resources, and support to develop
Remember: Every skill we have was learned at some point. Freedom to learn means giving all of us the chance to develop our full potential - even if that potential doesn't involve a hard hat. Remember: Every skill we have was learned at some point. Freedom to learn means giving all of us the chance to develop our full potential - even if that potential doesn't involve a hard hat.
---
## Sources & Evidence
### Education Funding Cuts
- Post-secondary education in Alberta has faced **$690 million in funding cuts** since 2019
- The University of Alberta alone eliminated **over 1,100 positions**
- Tuition increases have far outpaced inflation, with some programs seeing **40%+ increases**
- Source: [CUPE Alberta - Post-Secondary Cuts](https://cupe.ab.ca/)
- Source: [University of Alberta Budget Documents](https://www.ualberta.ca/)
### K-12 Education Concerns
- Per-student funding has declined when adjusted for inflation
- Class sizes have increased in many school divisions
- Educational assistants and support staff have been cut
- Rural schools face consolidation and reduced programming
- Source: [Alberta Teachers' Association](https://www.teachers.ab.ca/)
### Curriculum Changes
- New curriculum has faced criticism from education experts and teachers
- Indigenous education content remains insufficient according to TRC recommendations
- Science curriculum concerns raised about climate and evolution coverage
- Source: [Support Our Students Alberta](https://www.supportourstudents.ca/)
### Adult & Continuing Education
- Retraining programs for oil and gas workers remain underfunded relative to need
- Community adult learning programs have faced cuts
- Apprenticeship completion rates lag behind other provinces
- Source: [Alberta Federation of Labour - Just Transition Research](https://www.afl.org/)
### Digital Divide
- Rural Alberta faces significant gaps in internet access for online learning
- Low-income families face barriers to technology access
- Library funding cuts have reduced community learning resources
- Source: [Alberta Library Trustees Association](https://librarytrustees.ab.ca/)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -6,22 +6,22 @@
# Who is Responsible? # Who is Responsible?
[Call Premier Danielle Smith](tel:7804272251){ .md-button } [Call Premier Danielle Smith](tel:7804272251){ .md-button }
[Email Premier Smith](https://button.freealberta.org/embed/love-smith){ .md-button } [Email Premier Smith](https://influence.freealberta.org/campaign/love-smith){ .md-button }
[Call Deputy Premier Mike Ellis](tel:7804159550){ .md-button } [Call Deputy Premier Mike Ellis](tel:7804159550){ .md-button }
[Email Deputy Premier Ellis](https://button.freealberta.org/embed/love-ellis){ .md-button } [Email Deputy Premier Ellis](https://influence.freealberta.org/campaign/love-ellis){ .md-button }
[Call Searle Turton, Minister of Children and Family Services](tel:7806445255){ .md-button } [Call Searle Turton, Minister of Children and Family Services](tel:7806445255){ .md-button }
[Email Minister Turton](https://button.freealberta.org/embed/love-turton){ .md-button } [Email Minister Turton](https://influence.freealberta.org/campaign/love-turton){ .md-button }
[Call Dan Williams, Minister of Mental Health and Addiction](tel:7804270165){ .md-button } [Call Rick Wilson, Minister of Mental Health and Addiction](tel:7804270165){ .md-button }
[Email Minister Williams](https://button.freealberta.org/embed/love-williams){ .md-button } [Email Minister Williams](https://influence.freealberta.org/campaign/love-williams){ .md-button }
[Call Tanya Fir, Minister of Arts, Culture and Status of Women](tel:7804223559){ .md-button } [Call Tanya Fir, Minister of Arts, Culture and Status of Women](tel:7804223559){ .md-button }
[Email Minister of Arts and Culture](https://button.freealberta.org/embed/community-spaces){ .md-button } [Email Minister of Arts and Culture](https://influence.freealberta.org/campaign/community-spaces){ .md-button }
[Call Todd Hunter, Minister of Trade, Tourism and Investment](tel:7804278188){ .md-button } [Call Andrew Boitchenko, Minister of Tourism and Sport](tel:7804273070){ .md-button }
[Email Minister of Tourism](https://button.freealberta.org/embed/public-spaces){ .md-button } [Email Minister of Tourism](https://influence.freealberta.org/campaign/public-spaces){ .md-button }
## Why Do We Need Freedom to Love? ## Why Do We Need Freedom to Love?
@ -139,4 +139,45 @@ Remember: Love isn't a finite resource - letting others love freely doesn't dimi
!!! tip "Fun Fact" !!! tip "Fun Fact"
Did you know? Communities that embrace diversity and love freedom tend to have lower rates of mental health issues and higher rates of economic growth. It's almost like letting people be themselves is good for everyone. Did you know? Communities that embrace diversity and love freedom tend to have lower rates of mental health issues and higher rates of economic growth. It's almost like letting people be themselves is good for everyone.
---
## Sources & Evidence
### Alberta's Anti-LGBTQ+ Legislation
- In December 2024, Alberta passed **Bills 26, 27, and 29** - the most restrictive LGBTQ+ policies in Canada
- **Bill 26**: Bans gender-affirming care for youth under 18
- **Bill 27**: Requires parental permission for pronoun changes (under 16), effectively allowing schools to out LGBTQ+ students to unsupportive families
- **Bill 29**: Bans trans women from sports teams (age 12+)
- Source: [CBC News - Alberta trans legislation](https://www.cbc.ca/news/canada/edmonton/alberta-trans-bills-1.7089234)
### The Notwithstanding Clause
- A court **injunction blocked the care ban** (June 2025)
- In December 2025, the government **invoked the notwithstanding clause** to override Charter of Rights and Freedoms protections
- This is only the second time Alberta has used the notwithstanding clause
- Source: [Globe and Mail - Notwithstanding clause Alberta](https://www.theglobeandmail.com/)
### Medical & Professional Opposition
- The **Alberta Medical Association** opposed Bills 26, 27, 29
- The **Canadian Pediatric Society** opposed the legislation
- The **Alberta Teachers' Association** opposed Bill 27's pronoun notification requirements
- Mental health experts warn these policies **increase suicide risk** for LGBTQ+ youth
- Source: [Alberta Medical Association](https://www.albertadoctors.org/)
- Source: [Alberta Teachers' Association](https://www.teachers.ab.ca/)
### PM Trudeau Response
- Prime Minister Justin Trudeau called Alberta's policies "**most anti-LGBT of anywhere in the country**"
- Federal government considering intervention options
- Source: [CBC News - Trudeau on Alberta LGBTQ policies](https://www.cbc.ca/news/canada/edmonton/)
### Mental Health Impact
- LGBTQ+ youth face significantly higher mental health risks
- Wait times for psychiatric care in Alberta: **6 months to 1.5 years**
- Limited LGBTQ+-affirming mental health services available in the province
- Source: [Canadian Mental Health Association](https://cmha.ca/)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -5,14 +5,14 @@
# Who is Responsible? # Who is Responsible?
[Call Dan Williams, Minister of Mental Health and Addiction](tel:7804270165){ .md-button } [Call Rick Wilson, Minister of Mental Health and Addiction](tel:7804270165){ .md-button }
[Email Minister of Mental Health](https://button.freealberta.org/embed/mental-health-rest){ .md-button } [Email Minister of Mental Health](https://influence.freealberta.org/campaign/mental-health-rest){ .md-button }
[Call Jason Nixon, Minister of Seniors, Community and Social Services](tel:7806436210){ .md-button } [Call Jason Nixon, Minister of Assisted Living and Social Services](tel:7806436210){ .md-button }
[Send Minister Nixon a Email](https://button.freealberta.org/embed/rest-nixon){ .md-button } [Send Minister Nixon a Email](https://influence.freealberta.org/campaign/rest-nixon){ .md-button }
[Call Matt Jones, Minister of Jobs and Economy](tel:7804158165){ .md-button } [Call Joseph Schow, Minister of Jobs, Economy, Trade and Immigration](tel:7806448554){ .md-button }
[Email Minister of Jobs and Economy](https://button.freealberta.org/embed/work-life){ .md-button } [Email Minister of Jobs and Economy](https://influence.freealberta.org/campaign/work-life){ .md-button }
## Why Do We Need Freedom to Rest? ## Why Do We Need Freedom to Rest?
@ -125,4 +125,45 @@ Remember: Even our precious trucks need regular maintenance and downtime. We des
!!! tip "Fun Fact" !!! tip "Fun Fact"
Did we know? Countries with better work-life balance and more vacation time often have higher productivity rates. It's almost like treating people like humans works better than treating us like machines. Did we know? Countries with better work-life balance and more vacation time often have higher productivity rates. It's almost like treating people like humans works better than treating us like machines.
---
## Sources & Evidence
### Mental Health Crisis
- Alberta has only **10.6 psychiatrists per 100,000 people** - below the national average
- Wait times for mental health services: **6 months to 1.5 years**
- Alberta's suicide rate is **14.3 per 100,000** - higher than the national average
- Mental health funding has not kept pace with demand
- Source: [Canadian Institute for Health Information](https://www.cihi.ca/)
- Source: [Canadian Mental Health Association - Alberta](https://alberta.cmha.ca/)
### Work-Life Balance
- Alberta workers have some of the longest average work weeks in Canada
- Oil and gas industry workers frequently work **12+ hour shifts** on rotation schedules
- Burnout rates remain high across multiple sectors
- Source: [Statistics Canada - Labour Force Survey](https://www.statcan.gc.ca/)
### Labour Standards
- Alberta's minimum vacation entitlement is only **2 weeks** (below some other provinces)
- Many workers lack access to paid sick leave
- Gig economy workers often have no rest protections
- Source: [Alberta Employment Standards](https://www.alberta.ca/employment-standards.aspx)
### Healthcare Worker Burnout
- Healthcare workers face severe burnout following pandemic pressures
- Nursing shortages have led to mandatory overtime in some facilities
- Mental health supports for frontline workers remain inadequate
- Source: [United Nurses of Alberta](https://www.una.ca/)
### Economic Stress
- Cost of living increases have forced many to work multiple jobs
- Rental costs increased **17.5%** in a single year
- Financial stress is a leading contributor to mental health challenges
- Source: [Statistics Canada - Consumer Price Index](https://www.statcan.gc.ca/)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -3,11 +3,11 @@
!!! quote "Fresh Start Wisdom" !!! quote "Fresh Start Wisdom"
"Because sometimes the best way forward is to start from scratch." "Because sometimes the best way forward is to start from scratch."
[Call Matt Jones, Minister of Jobs, Economy and Trade](tel:7806448554){ .md-button } [Call Joseph Schow, Minister of Jobs, Economy, Trade and Immigration](tel:7806448554){ .md-button }
[Email Minister of Jobs and Economy](https://button.freealberta.org/embed/career-transition){ .md-button } [Email Minister of Jobs and Economy](https://influence.freealberta.org/campaign/career-transition){ .md-button }
[Call Searle Turton, Minister of Children and Family Services](tel:7806445255){ .md-button } [Call Searle Turton, Minister of Children and Family Services](tel:7806445255){ .md-button }
[Email Minister of Seniors and Community Services](https://button.freealberta.org/embed/social-support){ .md-button } [Email Minister of Assisted Living and Social Services](https://influence.freealberta.org/campaign/social-support){ .md-button }
## Why Do We Need Freedom to Start Over? ## Why Do We Need Freedom to Start Over?
@ -119,4 +119,46 @@ Remember: Even Alberta itself started over - we weren't always about oil and gas
!!! tip "Fun Fact" !!! tip "Fun Fact"
Did we know? The average person changes careers 5-7 times in their lifetime. It's almost like adapting to change is actually normal. Who would've thought? Did we know? The average person changes careers 5-7 times in their lifetime. It's almost like adapting to change is actually normal. Who would've thought?
---
## Sources & Evidence
### Energy Transition Challenges
- Alberta's economy remains heavily dependent on oil and gas extraction
- The renewable energy moratorium (2023-2024) stalled **24,000 jobs** in the clean energy sector
- Workers in fossil fuel industries face uncertain futures without adequate transition support
- "Just transition" programs remain underfunded compared to the scale of workforce affected
- Source: [Pembina Institute - Just Transition Research](https://www.pembina.org/)
- Source: [Alberta Federation of Labour](https://www.afl.org/)
### Retraining Programs
- Oil and gas workers retraining programs have limited enrollment capacity
- Apprenticeship and trade certification can take years to complete
- Many workers face age discrimination when seeking new careers
- Financial support during retraining is often inadequate to cover living expenses
- Source: [Alberta Labour and Immigration](https://www.alberta.ca/labour-and-immigration.aspx)
### Social Safety Net Gaps
- Income support rates leave recipients far below the poverty line
- Employment Insurance eligibility requirements exclude many gig and contract workers
- Housing costs have increased faster than any support program adjustments
- Source: [Government of Alberta - Income Support](https://www.alberta.ca/income-support.aspx)
### Bankruptcy and Debt
- Personal bankruptcies increased during economic downturns
- Student debt remains a barrier to career changes
- Consumer debt levels have risen significantly
- Source: [Office of the Superintendent of Bankruptcy Canada](https://www.ic.gc.ca/eic/site/bsf-osb.nsf/eng/home)
### Mental Health During Transitions
- Career transitions are associated with increased mental health challenges
- Support services for workers facing layoffs remain inadequate
- Stigma around seeking help persists, especially in traditionally male-dominated industries
- Source: [Canadian Mental Health Association - Alberta](https://alberta.cmha.ca/)
![freealbertalogo](../assets/freealberta-logo-long-opaque.gif) ![freealbertalogo](../assets/freealberta-logo-long-opaque.gif)

View File

@ -3,11 +3,11 @@
!!! warning "Settler Acknowledgment" !!! warning "Settler Acknowledgment"
As the admin team behind Free Alberta, we acknowledge that we're settlers on Treaty 6, 7, and 8 territories. We're typing these very words while sitting on land that was never ceded by the Cree, Dene, Blackfoot, Saulteaux, Nakota Sioux, Stoney Nakoda, Tsuu T'ina, and Métis peoples. We're grateful for everything this land and Mother Earth have provided us, even though "grateful" feels a bit weak when you're benefiting from a centuries-long land heist. Please read our sarcasm & humor throughout this document as mockery of capitalism and in no way disrespect for indigenous peoples. As the admin team behind Free Alberta, we acknowledge that we're settlers on Treaty 6, 7, and 8 territories. We're typing these very words while sitting on land that was never ceded by the Cree, Dene, Blackfoot, Saulteaux, Nakota Sioux, Stoney Nakoda, Tsuu T'ina, and Métis peoples. We're grateful for everything this land and Mother Earth have provided us, even though "grateful" feels a bit weak when you're benefiting from a centuries-long land heist. Please read our sarcasm & humor throughout this document as mockery of capitalism and in no way disrespect for indigenous peoples.
[Send Email to Minister Responsible for Landback](https://button.freealberta.org/embed/landback){ .md-button } [Send Email to Minister Responsible for Landback](https://influence.freealberta.org/campaign/landback){ .md-button }
[Call Rick Wilson, Honourable Minister of Indigenous Relations](tel:7804224144){ .md-button } [Call Rajan Sawhney, Minister of Indigenous Relations](tel:7804224144){ .md-button }
[Email Minister of Indigenous Relations](https://button.freealberta.org/embed/land-rights){ .md-button } [Email Minister of Indigenous Relations](https://influence.freealberta.org/campaign/land-rights){ .md-button }
[Call Todd Loewen, Minister of Forestry and Parks](tel:7806447353){ .md-button } [Call Todd Loewen, Minister of Forestry and Parks](tel:7806447353){ .md-button }
[Email Minister of Forestry and Parks](https://button.freealberta.org/embed/protected-areas){ .md-button } [Email Minister of Forestry and Parks](https://influence.freealberta.org/campaign/protected-areas){ .md-button }
## Oh, You Think You're Free? *laughs in Indigenous sovereignty* ## Oh, You Think You're Free? *laughs in Indigenous sovereignty*
@ -82,4 +82,40 @@ Remember: The most radical act of freedom is acknowledging our own role in oppre
--- ---
## Sources & Evidence
### Treaty Rights and Court Decisions
- All of Alberta is covered by **Treaties 6, 7, and 8** - encompassing 45 First Nations and 140 reserves
- In December 2025, a court ruled that **provincial separation without Indigenous consent violates treaty rights**
- Treaty 8 Nations have launched a court challenge over Crown land sales
- Beaver Lake Cree Nation's treaty infringement case went to trial in 2024
- Source: [Indigenous Watchdog - Treaty violations](https://www.indigenouswatchdog.org/)
- Source: [Macleans - A Sovereign Alberta Is a Treaty Violation](https://macleans.ca/politics/a-sovereign-alberta-is-a-treaty-violation/)
### Violence Against Indigenous Women
- The murder rate for Indigenous women in Alberta is **6.79 per 100,000** - drastically higher than non-Indigenous women
- Federal government announced $12 million over 10 years to study health impacts in oilsands region (after decades of concern)
- Source: [Statistics Canada - Indigenous peoples data](https://www.statcan.gc.ca/)
- Source: [MMIWG National Inquiry Final Report](https://www.mmiwg-ffada.ca/)
### Active Legal Cases
- Multiple First Nations are suing the Alberta government over treaty violations
- Sturgeon Lake Cree Nation is suing over Alberta Sovereignty Act changes
- Onion Lake Cree Nation is suing over the Alberta Sovereignty Act
- Siksika pursuing legal action over Ghost Dam relocation
- Treaty 6 Nations expressed disappointment over lack of consultation on federal-provincial MOU (November 2025)
- Source: [Environmental Law Centre - Cumulative Impacts on Treaty Rights](https://elc.ab.ca/post-library/cumulative-impacts-on-the-exercise-of-treaty-rights/)
### Consultation Failures
- No legislative acknowledgment of treaty rights exists in Alberta law
- Resource development routinely proceeds without meaningful consultation
- Courts have repeatedly upheld Indigenous rights against government actions
- Source: [Indigenous Bar Association](https://indigenousbar.ca/)
---
*This page is maintained by settlers trying to do better, one uncomfortable conversation at a time.* *This page is maintained by settlers trying to do better, one uncomfortable conversation at a time.*

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 52 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

253
scripts/create-campaigns.js Normal file
View File

@ -0,0 +1,253 @@
#!/usr/bin/env node
/**
* Script to create all Free Alberta campaigns from documentation
* Run with: node scripts/create-campaigns.js
*/
const https = require('https');
// NocoDB Configuration
const NOCODB_API_URL = 'https://db.freealberta.org';
const NOCODB_API_TOKEN = 'H3z5PEgvrC25LDRuvRpF1wuzsByG9PCem5DHyjPj';
const PROJECT_ID = 'plc0u50kgobr2xh';
const CAMPAIGNS_TABLE_ID = 'mh98emvhot9gjrg';
// Campaign definitions extracted from documentation
const campaigns = [
// Free To Learn
{ slug: 'learning-access', title: 'Access to Learning', buttonText: 'Email Minister of Education', target: ['Provincial'] },
{ slug: 'learn-glubish', title: 'Learning Policy Reform', buttonText: 'Email Minister Glubish', target: ['Provincial'] },
{ slug: 'higher-learning', title: 'Higher Learning Access', buttonText: 'Email Minister of Advanced Education', target: ['Provincial'] },
// Free To Rest
{ slug: 'mental-health-rest', title: 'Mental Health & Rest', buttonText: 'Email Minister of Mental Health', target: ['Provincial'] },
{ slug: 'rest-nixon', title: 'Work-Life Balance Policy', buttonText: 'Send Minister Nixon a Email', target: ['Provincial'] },
{ slug: 'work-life', title: 'Work-Life Balance', buttonText: 'Email Minister of Jobs and Economy', target: ['Provincial'] },
// Free To Love
{ slug: 'love-smith', title: 'Community & Connection', buttonText: 'Email Premier Smith', target: ['Provincial'] },
{ slug: 'love-ellis', title: 'Community Support', buttonText: 'Email Deputy Premier Ellis', target: ['Provincial'] },
{ slug: 'love-turton', title: 'Community Programs', buttonText: 'Email Minister Turton', target: ['Provincial'] },
{ slug: 'love-williams', title: 'Community Development', buttonText: 'Email Minister Williams', target: ['Provincial'] },
{ slug: 'community-spaces', title: 'Community Spaces', buttonText: 'Email Minister of Arts and Culture', target: ['Provincial', 'Municipal'] },
{ slug: 'public-spaces', title: 'Public Gathering Spaces', buttonText: 'Email Minister of Tourism', target: ['Provincial', 'Municipal'] },
// Free From Discrimination
{ slug: 'immigration-rights', title: 'Immigration Rights', buttonText: 'Email Minister of Immigration and Multiculturalism', target: ['Provincial', 'Federal'] },
{ slug: 'gender-equity', title: 'Gender Equity', buttonText: 'Email Minister of Arts, Culture and Status of Women', target: ['Provincial'] },
// Free To Start Over
{ slug: 'career-transition', title: 'Career Transition Support', buttonText: 'Email Minister of Jobs and Economy', target: ['Provincial'] },
{ slug: 'social-support', title: 'Social Support Services', buttonText: 'Email Minister of Seniors and Community Services', target: ['Provincial'] },
// Free From Surveillance
{ slug: 'privacy-rights', title: 'Privacy Rights', buttonText: 'Email the Premier', target: ['Provincial', 'Federal'] },
{ slug: 'tech-privacy', title: 'Technology Privacy', buttonText: 'Email Minister of Technology and Innovation', target: ['Provincial'] },
// Free From State Violence
{ slug: 'human-rights', title: 'Human Rights Protection', buttonText: 'Email Human Rights Commission', target: ['Provincial'] },
{ slug: 'ombudsman-investigation', title: 'Government Accountability', buttonText: 'Email Alberta Ombudsman', target: ['Provincial'] },
// Free To Create
{ slug: 'arts-support', title: 'Arts & Culture Support', buttonText: 'Email Minister of Arts and Culture', target: ['Provincial'] },
// Free From Colonization
{ slug: 'indigenous-relations', title: 'Indigenous Relations', buttonText: 'Email Minister of Indigenous Relations', target: ['Provincial', 'Federal'] },
{ slug: 'land-use', title: 'Land Use Policy', buttonText: 'Email Minister of Municipal Affairs', target: ['Provincial', 'Municipal'] },
// Free Needs: Environment
{ slug: 'environment-protection', title: 'Environmental Protection', buttonText: 'Email Minister of Environment', target: ['Provincial'] },
{ slug: 'clean-energy', title: 'Clean Energy Transition', buttonText: 'Email Minister of Energy', target: ['Provincial'] },
// Free To Associate
{ slug: 'association-rights-jobs', title: 'Worker Association Rights', buttonText: 'Email Minister of Jobs about Worker Associations', target: ['Provincial'] },
{ slug: 'cultural-association-rights', title: 'Cultural Association Rights', buttonText: 'Email Minister of Immigration about Cultural Associations', target: ['Provincial', 'Federal'] },
// Free From Police Violence
{ slug: 'police-accountability', title: 'Police Accountability', buttonText: 'Email Minister of Public Safety', target: ['Provincial', 'Municipal'] },
{ slug: 'asirt-investigation', title: 'Independent Police Investigations', buttonText: 'Email ASIRT Director', target: ['Provincial'] },
// Free Needs: Education
{ slug: 'education-reform', title: 'Education Reform', buttonText: 'Email Minister of Education', target: ['Provincial'] },
{ slug: 'higher-education', title: 'Higher Education Access', buttonText: 'Email Minister of Advanced Education', target: ['Provincial'] },
// Free From Corruption
{ slug: 'anti-corruption', title: 'Anti-Corruption Measures', buttonText: 'Email Minister of Public Safety', target: ['Provincial'] },
{ slug: 'legal-accountability', title: 'Legal System Accountability', buttonText: 'Email Minister of Justice', target: ['Provincial'] },
// Free To Gather
{ slug: 'assembly-rights', title: 'Freedom of Assembly', buttonText: 'Email Minister about Assembly Rights', target: ['Provincial'] },
{ slug: 'gathering-spaces-municipal', title: 'Community Gathering Spaces', buttonText: 'Email Minister about Community Spaces', target: ['Provincial', 'Municipal'] },
// Free From Government Overreach
{ slug: 'government-reform', title: 'Government Reform', buttonText: 'Email the Premier', target: ['Provincial'] },
{ slug: 'justice-reform', title: 'Justice System Reform', buttonText: 'Email Minister of Justice', target: ['Provincial'] },
// Free Needs: Housing
{ slug: 'seniors-housing', title: 'Seniors Housing', buttonText: 'Email Minister of Seniors and Community Services', target: ['Provincial'] },
{ slug: 'family-housing', title: 'Family Housing Support', buttonText: 'Email Minister of Children and Family Services', target: ['Provincial'] },
// Free From Corporate Control
{ slug: 'economic-reform', title: 'Economic Reform', buttonText: 'Email Minister of Jobs and Economy', target: ['Provincial'] },
{ slug: 'corporate-oversight', title: 'Corporate Oversight', buttonText: 'Email Minister of Trade and Tourism', target: ['Provincial'] },
// Free Needs: Water
{ slug: 'water-protection', title: 'Water Protection', buttonText: 'Email Minister of Environment', target: ['Provincial'] },
{ slug: 'water-access', title: 'Water Access', buttonText: 'Email Minister of Seniors and Community Services', target: ['Provincial', 'Municipal'] },
// Free Needs: Healthcare
{ slug: 'health-reform', title: 'Healthcare Reform', buttonText: 'Email Minister of Health', target: ['Provincial'] },
{ slug: 'mental-health', title: 'Mental Health Services', buttonText: 'Email Minister of Mental Health and Addiction', target: ['Provincial'] },
// Free Needs: Air Quality
{ slug: 'air-quality-protection', title: 'Air Quality Protection', buttonText: 'Email Minister about Air Quality Protection', target: ['Provincial'] },
{ slug: 'air-quality-health', title: 'Air Quality Health Impacts', buttonText: 'Email Minister about Health Impacts', target: ['Provincial'] },
// Land Back
{ slug: 'landback', title: 'Land Back Movement', buttonText: 'Send Email to Minister Responsible for Landback', target: ['Provincial', 'Federal'] },
{ slug: 'land-rights', title: 'Indigenous Land Rights', buttonText: 'Email Minister of Indigenous Relations', target: ['Provincial', 'Federal'] },
{ slug: 'protected-areas', title: 'Protected Areas', buttonText: 'Email Minister of Forestry and Parks', target: ['Provincial'] },
// Free Needs: Transportation
{ slug: 'transport-reform', title: 'Transportation Reform', buttonText: 'Email Minister of Transportation', target: ['Provincial'] },
{ slug: 'municipal-transit', title: 'Municipal Transit', buttonText: 'Email Minister of Municipal Affairs', target: ['Provincial', 'Municipal'] },
// Free Needs: Food
{ slug: 'food-security', title: 'Food Security', buttonText: 'Email Minister of Agriculture', target: ['Provincial'] },
{ slug: 'food-access', title: 'Food Access', buttonText: 'Email Minister of Seniors and Community Services', target: ['Provincial'] },
// Free Things: Communications
{ slug: 'tech-innovation', title: 'Technology & Innovation', buttonText: 'Email Minister of Technology and Innovation', target: ['Provincial'] },
{ slug: 'telecom-reform', title: 'Telecommunications Reform', buttonText: 'Email Minister of Service Alberta', target: ['Provincial', 'Federal'] },
// Free Things: Energy
{ slug: 'energy-reform', title: 'Energy Policy Reform', buttonText: 'Email Minister of Energy and Minerals', target: ['Provincial'] },
{ slug: 'utilities-reform', title: 'Utilities Reform', buttonText: 'Email Minister of Affordability and Utilities', target: ['Provincial'] },
// Free Things: Public Services
{ slug: 'public-services-reform', title: 'Public Services Reform', buttonText: 'Email Minister of Service Alberta', target: ['Provincial'] },
{ slug: 'public-services-vision', title: 'Public Services Vision', buttonText: 'Email the Premier', target: ['Provincial'] },
// Free Things: Recreation
{ slug: 'recreation-tourism', title: 'Recreation & Tourism', buttonText: 'Email Minister of Tourism and Sport', target: ['Provincial'] },
{ slug: 'recreation-parks', title: 'Parks & Recreation', buttonText: 'Email Minister of Forestry and Parks', target: ['Provincial'] },
// Archive (for completeness)
{ slug: 'startover', title: 'Starting Over Support', buttonText: 'Email Minister Jones', target: ['Provincial'] },
];
// Generate campaign descriptions based on title
function generateDescription(campaign) {
return `Take action! ${campaign.buttonText} to advocate for ${campaign.title.toLowerCase()} in Alberta. Your voice matters - let your elected representatives know that Albertans care about this issue.`;
}
// Generate email subject
function generateEmailSubject(campaign) {
return `Constituent Request: ${campaign.title}`;
}
// Generate email body
function generateEmailBody(campaign) {
return `Dear [Representative Name],
I am writing to you as a concerned constituent regarding ${campaign.title.toLowerCase()}.
This issue is important to me and many other Albertans. I urge you to take meaningful action to address this matter and represent the interests of your constituents.
I would appreciate a response outlining your position and any steps you plan to take.
Thank you for your service to our community.
Sincerely,
[Your Name]
[Your Postal Code]`;
}
// Create a campaign via NocoDB API
async function createCampaign(campaign) {
const data = {
'Campaign Slug': campaign.slug,
'Campaign Title': campaign.title,
'Description': generateDescription(campaign),
'Email Subject': generateEmailSubject(campaign),
'Email Body': generateEmailBody(campaign),
'Call to Action': campaign.buttonText,
'Status': 'active',
'Allow SMTP Email': true,
'Allow Mailto Link': true,
'Collect User Info': true,
'Show Email Count': true,
'Show Call Count': true,
'Allow Email Editing': true,
'Allow Custom Recipients': false,
'Show Response Wall Button': false,
'Target Government Levels': campaign.target,
};
return new Promise((resolve, reject) => {
const postData = JSON.stringify(data);
const options = {
hostname: 'db.freealberta.org',
port: 443,
path: `/api/v1/db/data/v1/${PROJECT_ID}/${CAMPAIGNS_TABLE_ID}`,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'xc-token': NOCODB_API_TOKEN,
'Content-Length': Buffer.byteLength(postData)
}
};
const req = https.request(options, (res) => {
let responseData = '';
res.on('data', (chunk) => {
responseData += chunk;
});
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve({ success: true, slug: campaign.slug, data: JSON.parse(responseData) });
} else {
reject(new Error(`Failed to create ${campaign.slug}: ${res.statusCode} - ${responseData}`));
}
});
});
req.on('error', (error) => {
reject(error);
});
req.write(postData);
req.end();
});
}
// Main execution
async function main() {
console.log(`Creating ${campaigns.length} campaigns...`);
console.log('='.repeat(50));
let successCount = 0;
let failCount = 0;
for (const campaign of campaigns) {
try {
const result = await createCampaign(campaign);
console.log(`✓ Created: ${campaign.slug} - "${campaign.title}"`);
successCount++;
// Small delay to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 100));
} catch (error) {
console.error(`✗ Failed: ${campaign.slug} - ${error.message}`);
failCount++;
}
}
console.log('='.repeat(50));
console.log(`Done! Created: ${successCount}, Failed: ${failCount}`);
console.log(`\nCampaigns are available at: https://influence.freealberta.org/campaign/{slug}`);
}
main().catch(console.error);

View File

@ -0,0 +1,71 @@
{
"cabinet_current": {
"as_of": "2026-01-15",
"source": "https://www.alberta.ca/premier-cabinet",
"ministers": [
{"name": "Danielle Smith", "title": "Premier and Minister of Intergovernmental and International Relations", "phone": "780-427-2251"},
{"name": "Mike Ellis", "title": "Deputy Premier and Minister of Public Safety and Emergency Services", "phone": "780-415-9550"},
{"name": "Nate Horner", "title": "President of Treasury Board and Minister of Finance", "phone": "780-415-4855"},
{"name": "Nathan Neudorf", "title": "Minister of Affordability and Utilities", "phone": "780-644-5135"},
{"name": "Mickey Amery", "title": "Minister of Justice", "phone": "780-427-2339"},
{"name": "Andrew Boitchenko", "title": "Minister of Tourism and Sport", "phone": "780-427-3070"},
{"name": "Devin Dreeshen", "title": "Minister of Transportation and Economic Corridors", "phone": "780-427-2080"},
{"name": "Tanya Fir", "title": "Minister of Arts, Culture and Status of Women", "phone": "780-422-3559"},
{"name": "Nate Glubish", "title": "Minister of Technology and Innovation", "phone": "780-644-8830"},
{"name": "Grant Hunter", "title": "Minister of Environment and Protected Areas", "phone": "780-427-2391"},
{"name": "Brian Jean", "title": "Minister of Energy and Minerals", "phone": "780-427-3740"},
{"name": "Matt Jones", "title": "Minister of Hospital and Surgical Health Services", "phone": "780-427-3665"},
{"name": "Adriana LaGrange", "title": "Minister of Primary and Preventative Health Services", "phone": "780-427-3665"},
{"name": "Todd Loewen", "title": "Minister of Forestry and Parks", "phone": "780-644-7353"},
{"name": "Martin Long", "title": "Minister of Infrastructure", "phone": "780-415-0507"},
{"name": "Myles McDougall", "title": "Minister of Advanced Education", "phone": "780-427-5777"},
{"name": "Dale Nally", "title": "Minister of Service Alberta and Red Tape Reduction", "phone": "780-427-2711"},
{"name": "Demetrios Nicolaides", "title": "Minister of Education and Childcare", "phone": "780-427-5010"},
{"name": "Jason Nixon", "title": "Minister of Assisted Living and Social Services", "phone": "780-643-6210"},
{"name": "Rajan Sawhney", "title": "Minister of Indigenous Relations", "phone": "780-422-4144"},
{"name": "Joseph Schow", "title": "Minister of Jobs, Economy, Trade and Immigration", "phone": "780-644-8554"},
{"name": "RJ Sigurdson", "title": "Minister of Agriculture and Irrigation", "phone": "780-427-2137"},
{"name": "Searle Turton", "title": "Minister of Children and Family Services", "phone": "780-644-5255"},
{"name": "Dan Williams", "title": "Minister of Municipal Affairs", "phone": "780-427-3744"},
{"name": "Rick Wilson", "title": "Minister of Mental Health and Addiction", "phone": "780-427-0165"}
],
"associate_ministers": [
{"name": "Muhammad Yaseen", "title": "Associate Minister of Multiculturalism", "phone": "780-644-2212"}
]
},
"corrections_needed": [
{"file": "landback.md", "old": "Rick Wilson, Honourable Minister of Indigenous Relations", "new": "Rajan Sawhney, Minister of Indigenous Relations", "phone": "780-422-4144"},
{"file": "freefrom/colonization.md", "old": "Rick Wilson, Minister of Indigenous Relations", "new": "Rajan Sawhney, Minister of Indigenous Relations", "phone": "780-422-4144"},
{"file": "freefrom/colonization.md", "old": "Ric McIver, Minister of Municipal Affairs", "new": "Dan Williams, Minister of Municipal Affairs", "phone": "780-427-3744"},
{"file": "freeneeds/transportation.md", "old": "Ric McIver, Minister of Municipal Affairs", "new": "Dan Williams, Minister of Municipal Affairs", "phone": "780-427-3744"},
{"file": "freeto/gather.md", "old": "Ric McIver, Minister of Municipal Affairs", "new": "Dan Williams, Minister of Municipal Affairs", "phone": "780-427-3744"},
{"file": "freeneeds/air.md", "old": "Rebecca Schulz, Minister of Environment", "new": "Grant Hunter, Minister of Environment and Protected Areas", "phone": "780-427-2391"},
{"file": "freeneeds/water.md", "old": "Rebecca Schulz, Minister of Environment and Protected Areas", "new": "Grant Hunter, Minister of Environment and Protected Areas", "phone": "780-427-2391"},
{"file": "freeneeds/environment.md", "old": "Rebecca Schulz, Minister of Environment and Protected Areas", "new": "Grant Hunter, Minister of Environment and Protected Areas", "phone": "780-427-2391"},
{"file": "freeneeds/healthcare.md", "old": "Dan Williams, Minister of Mental Health and Addiction", "new": "Rick Wilson, Minister of Mental Health and Addiction", "phone": "780-427-0165"},
{"file": "freeto/love.md", "old": "Dan Williams, Minister of Mental Health and Addiction", "new": "Rick Wilson, Minister of Mental Health and Addiction", "phone": "780-427-0165"},
{"file": "freeto/rest.md", "old": "Dan Williams, Minister of Mental Health and Addiction", "new": "Rick Wilson, Minister of Mental Health and Addiction", "phone": "780-427-0165"},
{"file": "freeneeds/education.md", "old": "Rajan Sawhney, Minister of Advanced Education", "new": "Myles McDougall, Minister of Advanced Education", "phone": "780-427-5777"},
{"file": "freeto/learn.md", "old": "Kaycee Madu, Minister of Advanced Education", "new": "Myles McDougall, Minister of Advanced Education", "phone": "780-427-5777"},
{"file": "freefrom/corporate.md", "old": "Matt Jones, Minister of Jobs and Economy", "new": "Joseph Schow, Minister of Jobs, Economy, Trade and Immigration", "phone": "780-644-8554"},
{"file": "freefrom/corporate.md", "old": "Todd Hunter, Minister of Trade, Tourism and Investment", "new": "Andrew Boitchenko, Minister of Tourism and Sport", "phone": "780-427-3070"},
{"file": "freeto/love.md", "old": "Todd Hunter, Minister of Trade, Tourism and Investment", "new": "Andrew Boitchenko, Minister of Tourism and Sport", "phone": "780-427-3070"},
{"file": "freeto/associate.md", "old": "Matt Jones, Minister of Jobs, Economy and Trade", "new": "Joseph Schow, Minister of Jobs, Economy, Trade and Immigration", "phone": "780-644-8554"},
{"file": "freeto/create.md", "old": "Matt Jones, Minister of Jobs, Economy and Trade", "new": "Joseph Schow, Minister of Jobs, Economy, Trade and Immigration", "phone": "780-644-8554"},
{"file": "freeto/startover.md", "old": "Matt Jones, Minister of Jobs, Economy and Trade", "new": "Joseph Schow, Minister of Jobs, Economy, Trade and Immigration", "phone": "780-644-8554"},
{"file": "freeto/rest.md", "old": "Matt Jones, Minister of Jobs and Economy", "new": "Joseph Schow, Minister of Jobs, Economy, Trade and Immigration", "phone": "780-644-8554"},
{"file": "freethings/recreation.md", "old": "Joseph Schow, Minister of Tourism and Sport", "new": "Andrew Boitchenko, Minister of Tourism and Sport", "phone": "780-427-3070"}
],
"title_updates_needed": [
{"old_title": "Minister of Seniors, Community and Social Services", "new_title": "Minister of Assisted Living and Social Services"},
{"old_title": "Minister of Seniors and Community Services", "new_title": "Minister of Assisted Living and Social Services"},
{"old_title": "Minister of Immigration and Multiculturalism", "new_title": "Associate Minister of Multiculturalism"},
{"old_title": "Minister of Education", "new_title": "Minister of Education and Childcare"},
{"old_title": "Minister of Public Safety", "new_title": "Minister of Public Safety and Emergency Services"},
{"old_title": "Minister of Environment", "new_title": "Minister of Environment and Protected Areas"},
{"old_title": "Minister of Energy", "new_title": "Minister of Energy and Minerals"},
{"old_title": "Minister of Transportation", "new_title": "Minister of Transportation and Economic Corridors"},
{"old_title": "Minister of Agriculture", "new_title": "Minister of Agriculture and Irrigation"},
{"old_title": "Minister of Service Alberta", "new_title": "Minister of Service Alberta and Red Tape Reduction"}
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

188
scripts/update-campaigns.js Normal file
View File

@ -0,0 +1,188 @@
#!/usr/bin/env node
/**
* Script to update campaign call-to-action text with correct minister titles
* Run with: node scripts/update-campaigns.js
*/
const https = require('https');
// NocoDB Configuration
const NOCODB_API_URL = 'https://db.freealberta.org';
const NOCODB_API_TOKEN = 'H3z5PEgvrC25LDRuvRpF1wuzsByG9PCem5DHyjPj';
const PROJECT_ID = 'plc0u50kgobr2xh';
const CAMPAIGNS_TABLE_ID = 'mh98emvhot9gjrg';
// Campaign updates - mapping slug to correct call-to-action text
const campaignUpdates = [
// Indigenous Relations - now Rajan Sawhney
{ slug: 'indigenous-relations', call_to_action: 'Email Minister of Indigenous Relations' },
{ slug: 'land-rights', call_to_action: 'Email Minister of Indigenous Relations' },
{ slug: 'landback', call_to_action: 'Email Minister Responsible for Landback' },
// Municipal Affairs - now Dan Williams
{ slug: 'land-use', call_to_action: 'Email Minister of Municipal Affairs' },
{ slug: 'municipal-transit', call_to_action: 'Email Minister of Municipal Affairs' },
{ slug: 'gathering-spaces-municipal', call_to_action: 'Email Minister of Municipal Affairs' },
// Environment - now Grant Hunter
{ slug: 'environment-protection', call_to_action: 'Email Minister of Environment and Protected Areas' },
{ slug: 'water-protection', call_to_action: 'Email Minister of Environment and Protected Areas' },
{ slug: 'air-quality-protection', call_to_action: 'Email Minister of Environment and Protected Areas' },
// Mental Health and Addiction - now Rick Wilson
{ slug: 'mental-health', call_to_action: 'Email Minister of Mental Health and Addiction' },
{ slug: 'mental-health-rest', call_to_action: 'Email Minister of Mental Health and Addiction' },
{ slug: 'love-williams', call_to_action: 'Email Minister of Mental Health and Addiction' },
// Advanced Education - now Myles McDougall
{ slug: 'higher-education', call_to_action: 'Email Minister of Advanced Education' },
{ slug: 'higher-learning', call_to_action: 'Email Minister of Advanced Education' },
// Jobs, Economy, Trade and Immigration - now Joseph Schow
{ slug: 'economic-reform', call_to_action: 'Email Minister of Jobs, Economy, Trade and Immigration' },
{ slug: 'career-transition', call_to_action: 'Email Minister of Jobs, Economy, Trade and Immigration' },
{ slug: 'work-life', call_to_action: 'Email Minister of Jobs, Economy, Trade and Immigration' },
{ slug: 'association-rights-jobs', call_to_action: 'Email Minister of Jobs, Economy, Trade and Immigration' },
// Tourism and Sport - now Andrew Boitchenko
{ slug: 'corporate-oversight', call_to_action: 'Email Minister of Tourism and Sport' },
{ slug: 'public-spaces', call_to_action: 'Email Minister of Tourism and Sport' },
{ slug: 'recreation-tourism', call_to_action: 'Email Minister of Tourism and Sport' },
// Assisted Living and Social Services - now Jason Nixon (updated title)
{ slug: 'social-support', call_to_action: 'Email Minister of Assisted Living and Social Services' },
{ slug: 'food-access', call_to_action: 'Email Minister of Assisted Living and Social Services' },
{ slug: 'water-access', call_to_action: 'Email Minister of Assisted Living and Social Services' },
{ slug: 'seniors-housing', call_to_action: 'Email Minister of Assisted Living and Social Services' },
// Associate Minister of Multiculturalism - Muhammad Yaseen (not full minister)
{ slug: 'immigration-rights', call_to_action: 'Email Associate Minister of Multiculturalism' },
{ slug: 'cultural-association-rights', call_to_action: 'Email Associate Minister of Multiculturalism' },
// Public Safety and Emergency Services - Mike Ellis (updated title)
{ slug: 'police-accountability', call_to_action: 'Email Minister of Public Safety and Emergency Services' },
{ slug: 'anti-corruption', call_to_action: 'Email Minister of Public Safety and Emergency Services' },
{ slug: 'assembly-rights', call_to_action: 'Email Minister of Public Safety and Emergency Services' },
// Energy and Minerals - Brian Jean (updated title)
{ slug: 'clean-energy', call_to_action: 'Email Minister of Energy and Minerals' },
{ slug: 'energy-reform', call_to_action: 'Email Minister of Energy and Minerals' },
// Education and Childcare - Demetrios Nicolaides (updated title)
{ slug: 'education-reform', call_to_action: 'Email Minister of Education and Childcare' },
{ slug: 'learning-access', call_to_action: 'Email Minister of Education and Childcare' },
// Transportation and Economic Corridors - Devin Dreeshen (updated title)
{ slug: 'transport-reform', call_to_action: 'Email Minister of Transportation and Economic Corridors' },
// Agriculture and Irrigation - RJ Sigurdson (updated title)
{ slug: 'food-security', call_to_action: 'Email Minister of Agriculture and Irrigation' },
// Service Alberta and Red Tape Reduction - Dale Nally (updated title)
{ slug: 'public-services-reform', call_to_action: 'Email Minister of Service Alberta and Red Tape Reduction' },
{ slug: 'telecom-reform', call_to_action: 'Email Minister of Service Alberta and Red Tape Reduction' },
// Affordability and Utilities - Nathan Neudorf
{ slug: 'utilities-reform', call_to_action: 'Email Minister of Affordability and Utilities' },
];
// Get campaign by slug
async function getCampaignBySlug(slug) {
return new Promise((resolve, reject) => {
const options = {
hostname: 'db.freealberta.org',
port: 443,
path: `/api/v1/db/data/v1/${PROJECT_ID}/${CAMPAIGNS_TABLE_ID}?where=(Campaign%20Slug,eq,${slug})`,
method: 'GET',
headers: {
'xc-token': NOCODB_API_TOKEN
}
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
const result = JSON.parse(data);
resolve(result.list && result.list.length > 0 ? result.list[0] : null);
} catch (e) {
reject(e);
}
});
});
req.on('error', reject);
req.end();
});
}
// Update campaign
async function updateCampaign(id, updates) {
return new Promise((resolve, reject) => {
const postData = JSON.stringify(updates);
const options = {
hostname: 'db.freealberta.org',
port: 443,
path: `/api/v1/db/data/v1/${PROJECT_ID}/${CAMPAIGNS_TABLE_ID}/${id}`,
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'xc-token': NOCODB_API_TOKEN,
'Content-Length': Buffer.byteLength(postData)
}
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve({ success: true });
} else {
reject(new Error(`Failed: ${res.statusCode} - ${data}`));
}
});
});
req.on('error', reject);
req.write(postData);
req.end();
});
}
async function main() {
console.log(`Updating ${campaignUpdates.length} campaigns with correct minister info...`);
console.log('='.repeat(60));
let successCount = 0;
let failCount = 0;
let notFoundCount = 0;
for (const update of campaignUpdates) {
try {
const campaign = await getCampaignBySlug(update.slug);
if (!campaign) {
console.log(`⚠ Not found: ${update.slug}`);
notFoundCount++;
continue;
}
const id = campaign.Id || campaign.ID || campaign.id;
await updateCampaign(id, { 'Call to Action': update.call_to_action });
console.log(`✓ Updated: ${update.slug} → "${update.call_to_action}"`);
successCount++;
// Small delay
await new Promise(resolve => setTimeout(resolve, 100));
} catch (error) {
console.error(`✗ Failed: ${update.slug} - ${error.message}`);
failCount++;
}
}
console.log('='.repeat(60));
console.log(`Done! Updated: ${successCount}, Failed: ${failCount}, Not found: ${notFoundCount}`);
}
main().catch(console.error);