diff --git a/configs/pangolin/resources.yml b/configs/pangolin/resources.yml index 66beba1f..a72d951e 100644 --- a/configs/pangolin/resources.yml +++ b/configs/pangolin/resources.yml @@ -64,7 +64,7 @@ resources: target_port: 80 required: false - - subdomain: git + - subdomain: gitea name: Gitea container: gitea-changemaker port: 3000 @@ -144,6 +144,14 @@ resources: target_port: 80 required: false + - subdomain: archive + name: Archive + container: archive-bnkops + port: 80 + target_ip: nginx + target_port: 80 + required: false + # Monitoring services (auto-detect profile) - subdomain: grafana name: Grafana diff --git a/docker-compose.yml b/docker-compose.yml index d7e7f5fe..9df51946 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1235,6 +1235,30 @@ services: profiles: - monitoring + # Archive site — serves preserved documentation from all versions + archive-site: + image: lscr.io/linuxserver/nginx:1.28.2 + container_name: archive-bnkops + restart: unless-stopped + ports: + - "127.0.0.1:4005:80" + volumes: + - /home/bunker-admin/archive-site/custom-landing:/config/www/landing:ro + - /home/bunker-admin/archive-site/site:/config/www/hub:ro + - /home/bunker-admin/archive-site/inserts:/config/www/inserts:ro + - /home/bunker-admin/archive-site/nginx.conf:/config/nginx/site-confs/default.conf:ro + - /home/bunker-admin/Archive/bnkops.changemaker-pre-v2/mkdocs/site:/config/www/pre-v2:ro + - /home/bunker-admin/changemaker.lite/mkdocs/site:/config/www/v2:ro + - /home/bunker-admin/my_bunker/site:/config/www/bunker:ro + - /home/bunker-admin/repo.bnkops.com/site:/config/www/repo:ro + - /home/bunker-admin/Landing Page - Dev/dist:/config/www/landing-page:ro + - /home/bunker-admin/Change-Maker-V3.9.7/site:/config/www/v397:ro + - /home/bunker-admin/Change-Maker-V3.8.7/site:/config/www/v387:ro + - /home/bunker-admin/Change-Maker-V3.9-TRBH-Production/site:/config/www/trbh:ro + - /home/bunker-admin/Change-Maker-V3.9-Pridecorner-Production/site:/config/www/pridecorner:ro + networks: + - changemaker-lite + # ============================================================================= # NETWORKS & VOLUMES # ============================================================================= diff --git a/mkdocs/docs/assets/landing/css/responsive.css b/mkdocs/docs/assets/landing/css/responsive.css new file mode 100644 index 00000000..eac2a88c --- /dev/null +++ b/mkdocs/docs/assets/landing/css/responsive.css @@ -0,0 +1,264 @@ +/* + * Responsive Stylesheet for BNKOps Website + * Version: 2.0.0 + * Date: May 2025 + * Theme: Dark Purple with Trans Pride Colors + */ + +/* ==================== + Responsive Design + ==================== */ + +/* Extra large devices (large desktops, 1200px and up) */ +@media (max-width: 1200px) { + .container { + max-width: 960px; + } + + .emoji-sticker { + font-size: 2.5rem; + } +} + +/* Large devices (desktops, 992px and up) */ +@media (max-width: 992px) { + .container { + max-width: 720px; + } + + section { + padding: 60px 0; + } + + .section-header h2 { + font-size: 2rem; + } + + .about-content, + .contact-content, + .error-container .container { + grid-template-columns: 1fr; + gap: 30px; + } + + .about-stats { + margin-top: 30px; + } + + .error-illustration { + display: none; + } + + .post-it { + transform: rotate(0deg) !important; + } + + .post-it:hover { + transform: scale(1.03) !important; + } +} + +/* Medium devices (tablets, 768px and up) */ +@media (max-width: 768px) { + .container { + max-width: 540px; + } + + .menu-toggle { + display: block; + } + + .nav-menu { + position: fixed; + top: 70px; + left: -100%; + background-color: rgba(16, 0, 43, 0.95); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + width: 100%; + height: calc(100vh - 70px); + flex-direction: column; + align-items: center; + justify-content: flex-start; + padding-top: 50px; + transition: var(--transition); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3); + z-index: 100; + } + + .nav-menu.active { + left: 0; + } + + .nav-menu li { + margin: 0 0 20px 0; + } + + .nav-menu a { + font-size: 1.2rem; + } + + .hero h1 { + font-size: 2.5rem; + } + + .services-grid { + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 20px; + } + + .about-stats { + grid-template-columns: 1fr; /* Change to single column for vertical stacking */ + gap: 25px; /* Increase gap for better separation */ + } + + /* Fix the stat items to remove any rotation and ensure consistency */ + .about-stats .stat-item { + max-width: 100%; + width: 100%; + margin: 0 auto; + transform: rotate(0deg) !important; /* Remove any rotation */ + } + + .about-stats .stat-item:hover { + transform: scale(1.03) !important; /* Only scale on hover, no rotation */ + } + + .emoji-sticker { + font-size: 2rem; + } + + .footer-content { + grid-template-columns: 1fr; + text-align: center; + } + + .footer-links h3::after, + .footer-social h3::after { + left: 50%; + transform: translateX(-50%); + } + + .social-icons { + justify-content: center; + } + + .error-actions { + flex-direction: column; + } + + .polaroid { + max-width: 220px; + padding: 10px 10px 40px 10px; + margin: 0 auto 20px; + } + + /* Make hero elements stack vertically instead of horizontal scrolling */ + .hero .flex-container { + flex-direction: column; + flex-wrap: wrap; + overflow-x: visible; + justify-content: center; + align-items: center; + gap: 25px; + } + + .hero .flex-container > div { + width: 100%; + max-width: 300px; + margin: 0 auto; + } + + /* Remove the horizontal scrollbar styling as it's no longer needed */ + .hero .flex-container::-webkit-scrollbar { + display: initial; + } + + .hero .flex-container { + -ms-overflow-style: initial; + scrollbar-width: initial; + } + + /* Ensure post-its have consistent styling */ + .hero .post-it { + width: 100% !important; + max-width: 100% !important; + box-sizing: border-box; + } +} + +/* Small devices (landscape phones, 576px and up) */ +@media (max-width: 576px) { + .container { + width: 95%; + padding: 0 10px; + } + + section { + padding: 50px 0; + } + + .section-header { + margin-bottom: 30px; + } + + .section-header h2 { + font-size: 1.8rem; + } + + .section-header p { + font-size: 1rem; + } + + .hero h1 { + font-size: 2rem; + } + + .hero p { + font-size: 1rem; + } + + .btn { + padding: 10px 20px; + font-size: 0.9rem; + } + + .service-card { + padding: 20px; + } + + .error-code { + font-size: 6rem; + } + + .error-content h1 { + font-size: 2rem; + } + + .sitemap-content { + grid-template-columns: 1fr; + } + + .emoji-sticker { + font-size: 1.5rem; + } + + .polaroid { + max-width: 220px; + } + + /* Adjust about stats spacing for smaller screens */ + .about-stats { + gap: 20px; /* Slightly reduce gap on very small screens */ + } + + .about-stats .stat-item { + padding: 15px; /* Reduce padding on very small screens */ + } + + /* Further adjust sizes for smaller screens */ + .hero .flex-container > div { + max-width: 100%; + width: 100%; + } +} diff --git a/mkdocs/docs/assets/landing/css/styles.css b/mkdocs/docs/assets/landing/css/styles.css new file mode 100644 index 00000000..92390f8f --- /dev/null +++ b/mkdocs/docs/assets/landing/css/styles.css @@ -0,0 +1,975 @@ +/* + * Main Stylesheet for BNKOps Website + * Version: 2.0.0 + * Date: May 2025 + */ + +/* ==================== + Base Styling + ==================== */ +:root { + /* Trans Pride Theme Colors */ + --trans-blue: #5BCEFA; + --trans-pink: #F5A9B8; + --trans-white: #FFFFFF; + + /* Primary dark purple theme */ + --primary-color: #7B2CBF; + --secondary-color: #9D4EDD; + --accent-color: #C77DFF; + + /* Additional theme colors */ + --dark-purple: #3C096C; + --light-purple: #E0AAFF; + + /* UI Colors */ + --light-color: #F8F9FA; + --dark-color: #240046; + --text-color: #F8F9FA; + --body-bg: #10002B; + --footer-bg: #240046; + --card-bg: #3C096C; + + /* Utility */ + --border-radius: 8px; + --box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25); + --transition: all 0.3s ease; + + /* Post-it note colors */ + --postit-blue: var(--trans-blue); + --postit-pink: var(--trans-pink); + --postit-white: var(--trans-white); + --postit-purple: var(--primary-color); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + line-height: 1.6; + color: var(--text-color); + background-color: var(--body-bg); + background-image: linear-gradient(to bottom, var(--dark-purple), var(--body-bg)); + min-height: 100vh; +} + +h1, h2, h3, h4, h5, h6 { + font-weight: 600; + line-height: 1.3; + margin-bottom: 1rem; +} + +p { + margin-bottom: 1rem; +} + +a { + color: var(--trans-blue); + text-decoration: none; + transition: var(--transition); +} + +a:hover { + color: var(--trans-pink); +} + +ul { + list-style: none; +} + +img { + max-width: 100%; + height: auto; +} + +.container { + width: 90%; + max-width: 1200px; + margin: 0 auto; + padding: 0 15px; +} + +section { + padding: 80px 0; +} + +.section-header { + text-align: center; + margin-bottom: 50px; + position: relative; +} + +.section-header h2 { + font-size: 2.5rem; + color: var(--trans-pink); + margin-bottom: 0.5rem; + position: relative; + display: inline-block; +} + +.section-header h2::after { + content: ''; + position: absolute; + width: 80px; + height: 4px; + background: linear-gradient(to right, var(--trans-blue), var(--trans-pink), var(--trans-white), var(--trans-pink), var(--trans-blue)); + bottom: -10px; + left: 50%; + transform: translateX(-50%); + border-radius: 2px; +} + +.section-header p { + color: var(--light-purple); + font-size: 1.1rem; + margin-top: 20px; +} + +/* ==================== + Post-it Notes Styling + ==================== */ +.post-it { + background-color: var(--card-bg); + border-radius: var(--border-radius); + padding: 30px; + box-shadow: var(--box-shadow); + transition: var(--transition); + position: relative; + z-index: 2; /* Add z-index to ensure post-its stay above emoji stickers */ + margin-bottom: 20px; + transform: rotate(0deg); +} + +.post-it-blue { + background-color: var(--postit-blue); + border-top: 8px solid #4AADDF; + color: #444; +} + +.post-it-pink { + background-color: var(--postit-pink); + border-top: 8px solid #E797A7; + color: #444; +} + +.post-it-white { + background-color: var(--postit-white); + border-top: 8px solid #E7E7E7; + color: #444; +} + +/* Add text shadow to post-its for better readability */ +.hero .post-it p { + text-shadow: 0 0 1px rgba(255, 255, 255, 0.5); + line-height: 1.5; +} + +.post-it-purple { + background-color: var(--primary-color); + border-top: 8px solid var(--accent-color); + color: white; +} + +.tilt-left-sm { + transform: rotate(-2deg); +} + +.tilt-right-sm { + transform: rotate(2deg); +} + +.tilt-left-md { + transform: rotate(-4deg); +} + +.tilt-right-md { + transform: rotate(4deg); +} + +.post-it:hover { + transform: scale(1.03) rotate(0deg); + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3); + z-index: 10; +} + +/* ==================== + Buttons + ==================== */ +.btn { + display: inline-block; + padding: 12px 28px; + font-size: 1rem; + font-weight: 600; + text-align: center; + border-radius: var(--border-radius); + transition: all 0.3s ease; + border: none; + cursor: pointer; + position: relative; + overflow: hidden; + z-index: 1; +} + +.btn::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(90deg, var(--trans-blue), var(--trans-pink), var(--trans-white), var(--trans-pink), var(--trans-blue)); + z-index: -1; + transition: transform 0.5s; + transform: translateX(-100%); +} + +.btn:hover::before { + transform: translateX(0); +} + +.btn:hover { + color: var(--dark-purple); + text-shadow: 0 0 3px rgba(255, 255, 255, 0.7); + font-weight: 700; +} + +.btn-primary { + background-color: var(--primary-color); + color: white; +} + +.btn-secondary { + background-color: var(--secondary-color); + color: white; +} + +.btn-primary:hover, .btn-secondary:hover { + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); + transform: translateY(-3px); +} + +/* ==================== + Navigation + ==================== */ +.navbar { + position: fixed; + top: 0; + left: 0; + width: 100%; + background-color: rgba(16, 0, 43, 0.9); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + z-index: 1000; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); + padding: 15px 0; + border-bottom: 1px solid rgba(123, 44, 191, 0.3); +} + +.navbar .container { + display: flex; + justify-content: space-between; + align-items: center; +} + +.logo a { + font-size: 1.8rem; + font-weight: 700; + color: var(--trans-white); + text-shadow: 0 0 10px rgba(91, 206, 250, 0.5), 0 0 20px rgba(245, 169, 184, 0.3); + position: relative; +} + +.logo a::after { + content: ''; + position: absolute; + width: 100%; + height: 3px; + bottom: -5px; + left: 0; + background: linear-gradient(to right, var(--trans-blue), var(--trans-pink), var(--trans-white)); + border-radius: 3px; +} + +.nav-menu { + display: flex; +} + +.nav-menu li { + margin-left: 30px; +} + +.nav-menu a { + color: var(--light-color); + font-weight: 500; + position: relative; + padding-bottom: 5px; +} + +.nav-menu a:after { + content: ''; + position: absolute; + width: 0; + height: 2px; + background: linear-gradient(to right, var(--trans-blue), var(--trans-pink)); + bottom: 0; + left: 0; + transition: var(--transition); +} + +.nav-menu a:hover:after, +.nav-menu a.active:after { + width: 100%; +} + +.nav-menu a.active { + color: var(--trans-pink); +} + +.menu-toggle { + display: none; + cursor: pointer; +} + +.menu-toggle .bar { + width: 25px; + height: 3px; + background-color: var(--light-color); + margin: 5px 0; + transition: var(--transition); + display: block; +} + +/* ==================== + Hero Section + ==================== */ +.hero { + min-height: 100vh; + display: flex; + align-items: center; + background: linear-gradient(rgba(16, 0, 43, 0.8), rgba(36, 0, 70, 0.9)), url('../img/hero-bg.jpg') center/cover no-repeat; + color: white; + text-align: center; + padding-top: 80px; + position: relative; + overflow: hidden; + z-index: 1; /* Add z-index to ensure content stays above emoji stickers */ +} + +.hero::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 10px; + background: linear-gradient(90deg, var(--trans-blue) 0%, var(--trans-pink) 50%, var(--trans-white) 100%); +} + +.hero h1 { + font-size: 3.5rem; + margin-bottom: 20px; + color: var(--trans-white); + text-shadow: 0 0 15px rgba(123, 44, 191, 0.8); + position: relative; +} + +.hero h1::after { + content: ''; + position: absolute; + width: 120px; + height: 4px; + background: linear-gradient(to right, var(--trans-blue), var(--trans-pink), var(--trans-white)); + bottom: -10px; + left: 50%; + transform: translateX(-50%); + border-radius: 2px; +} + +/* Add base styles for the flex container */ +.hero .flex-container { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 20px; + margin-top: 40px; + width: 100%; + position: relative; +} + +.hero p { + font-size: 1.2rem; + margin-bottom: 30px; + max-width: 700px; + margin-left: auto; + margin-right: auto; + color: var(--trans-white); + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); +} + +.emoji-sticker { + position: absolute; + font-size: 3rem; + z-index: -1; /* Changed from 0 to -1 to ensure it's behind other elements */ + animation: float 5s ease-in-out infinite; + filter: drop-shadow(0 0 10px rgba(0, 0, 0, 0.3)); + pointer-events: none; /* So they don't interfere with clicks */ + opacity: 0.8; +} + +@keyframes float { + 0% { + transform: translateY(0px); + } + 50% { + transform: translateY(-15px); + } + 100% { + transform: translateY(0px); + } +} + +/* Polaroid styling */ +.polaroid { + background-color: white; + padding: 15px 15px 45px 15px; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); + max-width: 300px; + margin: 20px auto; + transform: rotate(-3deg); + transition: all 0.3s ease; + position: relative; + z-index: 5; + flex-shrink: 0; /* Prevent shrinking in flexbox */ +} + +.polaroid:hover { + transform: rotate(0deg) scale(1.05); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.4); +} + +.polaroid-inner { + position: relative; + overflow: hidden; +} + +.polaroid-img { + width: 100%; + height: auto; + display: block; + transition: transform 0.5s ease; +} + +.polaroid:hover .polaroid-img { + transform: scale(1.03); +} + +.polaroid-caption { + text-align: center; + position: absolute; + bottom: 15px; + left: 0; + right: 0; +} + +.polaroid-caption p { + font-family: 'Segoe UI', Tahoma, sans-serif; + color: #333; + font-size: 0.9rem; + margin: 0; + font-weight: 500; + letter-spacing: 0.5px; +} + +/* ==================== + Services Section + ==================== */ +.services { + background-color: var(--body-bg); + position: relative; + z-index: 5; +} + +.services::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: radial-gradient(circle at 10% 20%, rgba(123, 44, 191, 0.1) 0%, transparent 60%), + radial-gradient(circle at 80% 70%, rgba(245, 169, 184, 0.1) 0%, transparent 60%); + z-index: -1; +} + +.services-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 30px; +} + +.service-card { + background-color: var(--card-bg); + padding: 30px; + border-radius: var(--border-radius); + box-shadow: var(--box-shadow); + text-align: center; + transition: var(--transition); + color: var(--text-color); + position: relative; + z-index: 2; /* Add z-index to ensure cards stay above emoji stickers */ + overflow: hidden; + border-top: 4px solid transparent; +} + +.service-card:nth-child(3n+1) { + border-color: var(--trans-blue); +} + +.service-card:nth-child(3n+2) { + border-color: var(--trans-pink); +} + +.service-card:nth-child(3n+3) { + border-color: var(--trans-white); +} + +.service-card:hover { + transform: translateY(-10px); + box-shadow: 0 15px 30px rgba(0, 0, 0, 0.4); +} + +.service-card::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 5px; + background: linear-gradient(90deg, var(--trans-blue), var(--trans-pink), var(--trans-white)); + opacity: 0; + transition: opacity 0.3s ease; +} + +.service-card:hover::after { + opacity: 1; +} + +.service-card i { + font-size: 2.5rem; + color: var(--accent-color); + margin-bottom: 20px; + display: inline-block; + transition: transform 0.3s ease; +} + +.service-card:hover i { + transform: scale(1.2); +} + +.service-card h3 { + font-size: 1.5rem; + margin-bottom: 15px; + color: var (--trans-white); +} + +/* ==================== + About Section + ==================== */ +.about { + background-color: var(--dark-purple); + position: relative; +} + +.about-content { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 50px; + align-items: center; +} + +.about-text { + color: var(--text-color); +} + +/* Add specific styling for the about text card */ +.about-text .post-it { + border-left: 4px solid var(--trans-pink); + padding: 25px 30px; + margin-bottom: 0; +} + +.about-text .post-it p:last-child { + margin-bottom: 0; +} + +.about-text p:last-child { + margin-bottom: 0; +} + +.about-stats { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 20px; +} + +.stat-item { + background-color: var(--card-bg); + padding: 20px; + border-radius: var(--border-radius); + box-shadow: var(--box-shadow); + text-align: center; + color: var(--text-color); + transition: var(--transition); +} + +.stat-item:hover { + transform: translateY(-5px); + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3); +} + +.stat-item h3 { + font-size: 2.5rem; + background: linear-gradient(90deg, var(--trans-blue), var(--trans-pink)); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + margin-bottom: 10px; +} + +/* ==================== + Contact Section + ==================== */ +.contact { + background-color: var(--body-bg); + position: relative; +} + +.contact::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: radial-gradient(circle at 90% 10%, rgba(123, 44, 191, 0.2) 0%, transparent 60%), + radial-gradient(circle at 20% 90%, rgba(245, 169, 184, 0.2) 0%, transparent 60%); + z-index: 0; +} + +.contact-content { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 50px; + position: relative; + z-index: 2; /* Change from 1 to 2 for consistency with other elements */ +} + +.contact-item { + display: flex; + margin-bottom: 30px; +} + +.contact-item i { + font-size: 1.5rem; + color: var(--trans-pink); + margin-right: 20px; + width: 40px; + height: 40px; + background-color: rgba(123, 44, 191, 0.2); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + transition: var(--transition); +} + +.contact-item:hover i { + background-color: var(--primary-color); + color: var(--trans-white); + transform: scale(1.1); +} + +.contact-item h3 { + font-size: 1.2rem; + margin-bottom: 5px; + color: var(--trans-blue); +} + +.contact-item p { + color: var (--text-color); +} + +.form-group { + margin-bottom: 20px; +} + +.form-group input, +.form-group textarea { + width: 100%; + padding: 12px 15px; + background-color: rgba(60, 9, 108, 0.5); + border: 1px solid var(--primary-color); + border-radius: var(--border-radius); + font-size: 1rem; + transition: var(--transition); + color: var(--text-color); + box-shadow: 0 0 10px rgba(123, 44, 191, 0.1) inset; +} + +.form-group input::placeholder, +.form-group textarea::placeholder { + color: rgba(248, 249, 250, 0.5); +} + +.form-group input:focus, +.form-group textarea:focus { + border-color: var(--trans-pink); + outline: none; + box-shadow: 0 0 15px rgba(245, 169, 184, 0.3); +} + +.form-group textarea { + height: 150px; + resize: vertical; +} + +/* ==================== + Footer + ==================== */ +footer { + background-color: var(--footer-bg); + color: var(--text-color); + padding: 70px 0 20px; + position: relative; +} + +footer::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 5px; + background: linear-gradient(90deg, var(--trans-blue), var(--trans-pink), var(--trans-white), var(--trans-pink), var(--trans-blue)); +} + +.footer-content { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 30px; + margin-bottom: 50px; +} + +.footer-logo h2 { + font-size: 2rem; + margin-bottom: 10px; + background: linear-gradient(90deg, var(--trans-blue), var(--trans-pink)); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; +} + +.footer-links h3, +.footer-social h3 { + font-size: 1.2rem; + margin-bottom: 20px; + position: relative; + padding-bottom: 10px; + color: var(--trans-white); +} + +.footer-links h3::after, +.footer-social h3::after { + content: ''; + position: absolute; + left: 0; + bottom: 0; + width: 50px; + height: 2px; + background: linear-gradient(to right, var(--trans-blue), var(--trans-pink)); +} + +.footer-links ul li { + margin-bottom: 10px; +} + +.footer-links ul li a { + color: var(--light-purple); + transition: var(--transition); + display: inline-block; +} + +.footer-links ul li a:hover { + color: var(--trans-pink); + padding-left: 5px; +} + +.social-icons { + display: flex; + gap: 15px; +} + +.social-icons a { + width: 40px; + height: 40px; + background-color: rgba(60, 9, 108, 0.6); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-color); + transition: var(--transition); + position: relative; + overflow: hidden; +} + +.social-icons a::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(45deg, var(--trans-blue), var(--trans-pink)); + opacity: 0; + transition: opacity 0.3s ease; + z-index: -1; +} + +.social-icons a:hover::before { + opacity: 1; +} + +.social-icons a:hover { + transform: translateY(-5px); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); +} + +.footer-bottom { + text-align: center; + padding-top: 20px; + border-top: 1px solid rgba(245, 169, 184, 0.2); + color: var (--light-purple); +} + +/* ==================== + Sitemap Page + ==================== */ +.sitemap-section { + padding-top: 150px; + min-height: calc(100vh - 300px); +} + +.sitemap-content { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 30px; +} + +.sitemap-group { + background-color: var(--card-bg); + padding: 30px; + border-radius: var(--border-radius); + box-shadow: var(--box-shadow); +} + +.sitemap-group h2 { + color: var(--trans-pink); + margin-bottom: 20px; + padding-bottom: 10px; + border-bottom: 2px solid var(--primary-color); +} + +.sitemap-list li { + margin-bottom: 10px; + padding-left: 20px; + position: relative; +} + +.sitemap-list li::before { + content: '→'; + position: absolute; + left: 0; + color: var(--trans-blue); +} + +.sitemap-list li a { + color: var(--text-color); + transition: var(--transition); +} + +.sitemap-list li a:hover { + color: var(--trans-pink); +} + +.sitemap-list ul { + margin-top: 10px; + margin-left: 20px; +} + +/* ==================== + Error Page (404) + ==================== */ +.error-page { + background-color: var(--body-bg); +} + +.error-container { + padding-top: 150px; + min-height: calc(100vh - 300px); + display: flex; + align-items: center; +} + +.error-container .container { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 50px; + align-items: center; +} + +.error-code { + font-size: 8rem; + font-weight: 700; + background: linear-gradient(90deg, var(--trans-blue), var(--trans-pink)); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + line-height: 1; + margin-bottom: 20px; + text-shadow: 0 5px 30px rgba(0, 0, 0, 0.2); +} + +.error-content h1 { + font-size: 2.5rem; + margin-bottom: 20px; + color: var(--trans-white); +} + +.error-content p { + color: var(--light-purple); +} + +.error-actions { + margin-top: 30px; + display: flex; + gap: 15px; +} + +.error-illustration { + text-align: center; +} + +.error-illustration i { + font-size: 15rem; + color: var(--primary-color); + opacity: 0.3; +} diff --git a/mkdocs/docs/assets/landing/img/bnkops-logo-purple.png b/mkdocs/docs/assets/landing/img/bnkops-logo-purple.png new file mode 100644 index 00000000..6b010504 Binary files /dev/null and b/mkdocs/docs/assets/landing/img/bnkops-logo-purple.png differ diff --git a/mkdocs/docs/assets/landing/img/bunker-logo.svg b/mkdocs/docs/assets/landing/img/bunker-logo.svg new file mode 100644 index 00000000..a388854c --- /dev/null +++ b/mkdocs/docs/assets/landing/img/bunker-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/mkdocs/docs/assets/landing/js/main.js b/mkdocs/docs/assets/landing/js/main.js new file mode 100644 index 00000000..af168ae1 --- /dev/null +++ b/mkdocs/docs/assets/landing/js/main.js @@ -0,0 +1,189 @@ +/* + * Main JavaScript for BNKOps Website + * Version: 2.0.0 + * Date: May 2025 + * Theme: Dark Purple with Trans Pride Colors + */ + +document.addEventListener('DOMContentLoaded', () => { + // Mobile menu toggle + const mobileMenu = document.getElementById('mobile-menu'); + const navMenu = document.querySelector('.nav-menu'); + + if (mobileMenu) { + mobileMenu.addEventListener('click', () => { + mobileMenu.classList.toggle('active'); + navMenu.classList.toggle('active'); + }); + } + + // Close mobile menu when clicking on a nav link + const navLinks = document.querySelectorAll('.nav-menu a'); + + navLinks.forEach(link => { + link.addEventListener('click', () => { + mobileMenu.classList.remove('active'); + navMenu.classList.remove('active'); + }); + }); + + // Smooth scrolling for anchor links + document.querySelectorAll('a[href^="#"]').forEach(anchor => { + anchor.addEventListener('click', function(e) { + if (this.getAttribute('href') !== '#') { + e.preventDefault(); + + const targetId = this.getAttribute('href'); + const targetElement = document.querySelector(targetId); + + if (targetElement) { + window.scrollTo({ + top: targetElement.offsetTop - 70, + behavior: 'smooth' + }); + } + } + }); + }); + + // Sticky navigation on scroll with color change + const navbar = document.querySelector('.navbar'); + const navbarHeight = navbar.getBoundingClientRect().height; + + window.addEventListener('scroll', () => { + if (window.scrollY > navbarHeight) { + navbar.classList.add('sticky'); + navbar.style.backgroundColor = 'rgba(60, 9, 108, 0.95)'; + } else { + navbar.classList.remove('sticky'); + navbar.style.backgroundColor = 'rgba(16, 0, 43, 0.9)'; + } + }); + + // Form submission (prevent default for demo) + const contactForm = document.querySelector('.contact-form'); + + if (contactForm) { + contactForm.addEventListener('submit', (e) => { + e.preventDefault(); + alert('Form submission successful! This is a demo message.'); + contactForm.reset(); + }); + } + + // Create emoji stickers dynamically + const createEmojiStickers = () => { + const heroSection = document.querySelector('.hero'); + const servicesSection = document.querySelector('.services'); + const aboutSection = document.querySelector('.about'); + const contactSection = document.querySelector('.contact'); + + const emojis = ['💻', '📚', '🌱', '⚡', '🔓', '💪', '✨', '🌈', '🏳️‍⚧️', '🏳️‍🌈']; + + const sections = [heroSection, servicesSection, aboutSection, contactSection]; + + // Safe zones for emoji placement (percentage from edges) + const safeZones = { + hero: { top: 35, bottom: 25, left: 10, right: 10 }, + services: { top: 15, bottom: 15, left: 5, right: 5 }, + about: { top: 20, bottom: 20, left: 5, right: 5 }, + contact: { top: 20, bottom: 15, left: 5, right: 5 } + }; + + // Get section name from class + const getSectionName = (section) => { + if (section.classList.contains('hero')) return 'hero'; + if (section.classList.contains('services')) return 'services'; + if (section.classList.contains('about')) return 'about'; + if (section.classList.contains('contact')) return 'contact'; + return 'default'; + }; + + sections.forEach((section) => { + if (section) { + const sectionName = getSectionName(section); + const zone = safeZones[sectionName] || { top: 20, bottom: 20, left: 5, right: 5 }; + + // Add 3-4 emoji stickers per section + const emojiCount = Math.floor(Math.random() * 2) + 2; // 2-3 emojis per section + const placedPositions = []; + + for (let i = 0; i < emojiCount; i++) { + const emoji = document.createElement('div'); + emoji.className = 'emoji-sticker'; + emoji.textContent = emojis[Math.floor(Math.random() * emojis.length)]; + + // Calculate safe position that doesn't overlap with content + let attempts = 0; + let position; + + // Try to find a non-overlapping position + do { + position = { + top: Math.random() * (100 - zone.top - zone.bottom) + zone.top, + left: Math.random() * (100 - zone.left - zone.right) + zone.left + }; + + // Check if this position is far enough from other emojis + const isFarEnough = placedPositions.every(pos => { + const distance = Math.sqrt( + Math.pow(position.top - pos.top, 2) + + Math.pow(position.left - pos.left, 2) + ); + return distance > 20; // Minimum distance between emojis + }); + + attempts++; + if (isFarEnough || attempts > 10) break; + } while (attempts < 10); + + // Apply position + emoji.style.top = `${position.top}%`; + emoji.style.left = `${position.left}%`; + placedPositions.push(position); + + // Set z-index to be below content + emoji.style.zIndex = "-1"; + + // Random animation delay + emoji.style.animationDelay = `${Math.random() * 2}s`; + + section.appendChild(emoji); + } + } + }); + }; + + // Create post-it notes effect + const createPostItEffect = () => { + const postIts = document.querySelectorAll('.post-it, .service-card'); + + postIts.forEach((postIt, index) => { + // Add subtle rotation to every other card + if (index % 2 === 0) { + postIt.classList.add('tilt-left-sm'); + } else { + postIt.classList.add('tilt-right-sm'); + } + + // Create shadow effect on hover + postIt.addEventListener('mouseover', () => { + postIt.style.transform = 'scale(1.03) rotate(0deg)'; + postIt.style.boxShadow = '0 15px 30px rgba(0, 0, 0, 0.3)'; + postIt.style.zIndex = '10'; + }); + + postIt.addEventListener('mouseout', () => { + postIt.style.transform = ''; + postIt.style.boxShadow = ''; + postIt.style.zIndex = '2'; + }); + }); + }; + + // Initialize effects + setTimeout(() => { + createEmojiStickers(); + createPostItEffect(); + }, 100); +}); diff --git a/mkdocs/docs/overrides/lander.html b/mkdocs/docs/overrides/lander.html index 37ace877..6020f4dc 100644 --- a/mkdocs/docs/overrides/lander.html +++ b/mkdocs/docs/overrides/lander.html @@ -1,3642 +1,253 @@ - + - Changemaker Lite - Grow Power. Don't Rent It. - - - - - - - - - - - - - - - + The Bunker Operations + + + + + + + + + - - - - - - - - - - - -
- -
-
- - -
- -
-
Self-Hosted Campaign Infrastructure
-

Grow Power.
Don't Rent It.

-

- Run your campaigns, canvassing, fundraising, team chat, media, and more — all on your own infrastructure. - No corporate surveillance. No foreign interference. No monthly ransoms. - A free* and open source toolkit built for growing political movements. -

- - - - - -
-
-
100%
-
Data Ownership
-
-
-
$0
-
Self-Hosted
-
-
-
45+
-
Integrated Tools
-
-
-
FOSS
-
Open Source
-
-
-
-
- - -
-
-
-

Disconnected Roots

-

Traditional campaign tools weren't built for the reality of political organizing

-
-
-
-
📱
-

Can't Find Answers Fast

-

Voters ask tough questions. Your team fumbles through PDFs, emails, and scattered Google Docs while the voter loses interest.

-
-
-
🗺
-

Disconnected Data

-

Walk lists in one app, voter info in another, campaign policies somewhere else. Nothing talks to each other.

-
-
-
💸
-

Death by Subscription

-

$100 here, $500 there. Thousands monthly on tools that don't work together and hold your data hostage.

-
-
-
🔒
-

No Data Control

-

Your strategies in corporate clouds. Your movement's future in someone else's hands. Export? Good luck.

-
-
-
📵
-

Not Mobile-Ready

-

Desktop-first tools that barely work on phones. Canvassers struggling with tiny text and broken interfaces in the field.

-
-
-
🏢
-

Foreign Dependencies

-

US companies with US regulations. Your Canadian campaign data subject to foreign laws and surveillance.

-
-
-
-
- - -
-
-
-
-

Software Should Grow Power, Not Extract It

-

Most campaign and political software is extractive by nature — designed to pull information from a community in order to influence politics. Your voter data in corporate clouds. Your strategies readable by foreign jurisdictions. Your movement’s future in someone else’s hands.

-

Changemaker asks a different question: “what tools are needed to grow change in a community?” Growing change means making connections between people, providing access to tools that create new opportunities, and deeply understanding the wants and needs of your movement — on infrastructure you control.

-

Organizational independence requires technological independence. Socialist movements will never outspend capital — but a thousand neighborhood mailing lists has more potential impact than any single organization. Workers, with the right tools, will build the future.

-
-
-
-
🤝
-

Distribute Power

-

Decentralized organizing is the way out. When knowledge and tools are widely distributed — not gatekept by leadership or locked behind vendor paywalls — movements become resilient.

-
-
-
🔒
-

Own Your Secrets

-

If you do politics, who is reading your secrets? Corporate platforms extract intelligence systematically. Self-hosted infrastructure means your strategies stay yours — no algorithmic surveillance, no foreign data laws, no backdoors.

-
-
-
🌱
-

De-Corp Your Stack

-

Every subscription to corporate software funds the machine you’re fighting. Free and open source tools reduce dependence on capital, eliminate vendor lock-in, and keep your movement’s resources where they belong — in the community.

-
-
- -
-
-
- - -
-
-
-

The Network

-

50+ tools connected — each node strengthens the whole

-
- - -
-
-
📨
-
-

Communication

-

Email campaigns, SMS outreach, newsletters, advocacy, and team chat

-
-
-
- Public campaigns page with postal code representative lookup and email advocacy -
Public Campaigns — postal code lookup to find representatives and send advocacy emails
-
-
-
-
-
📬
-

Listmonk Newsletters

-
-

Full newsletter platform with subscriber management, templates, and analytics. Drop-in replacement for Mailchimp.

-
Unlimited subscribersTemplatesAnalytics
-
-
-
-
🎯
-

Influence Campaigns

-
-

Postal code to representative lookup. Automated advocacy emails to elected officials with tracking and response collection.

-
Rep lookupBullMQ queueTracking
-
-
-
-
-

Email Templates

-
-

GrapesJS visual email editor with variable substitution, versioning, and instant preview. Build once, send everywhere.

-
Visual editorVariablesVersioning
-
-
-
-
💬
-

Response Wall

-
-

Public response collection with moderation, upvoting, and verification. Showcase supporter voices on your campaigns.

-
ModerationUpvotingVerification
-
-
-
-
🚀
-

Rocket.Chat

-
-

Self-hosted team chat with SSO integration. Automatic channel notifications for shift signups, canvass sessions, and campaign responses.

-
SSOChannelsSlack alternative
-
-
-
-
🔔
-

Smart Notifications

-
-

Async notification queue for admin alerts and volunteer feedback. Shift reminders, session summaries, and signup confirmations.

-
BullMQRemindersSummaries
-
-
-
-
📱
-

SMS Campaigns

-
-

Text message outreach via Termux Android bridge. Contact lists, templated campaigns, delivery tracking, response sync, and device health monitoring.

-
Termux bridgeBullMQ queueResponse sync
-
-
-
-
🗨
-

Chat Widget

-
-

Floating Rocket.Chat panel for logged-in team members. Minimizable FAB, auth-gated access, and settings-toggleable visibility across the admin interface.

-
Rocket.ChatAuth-gatedFloating
-
-
-
- - -
-
-
🗺
-
-

Mapping & Canvassing

-

GPS tracking, door-to-door canvassing, geographic organization

-
-
-
- Public interactive map showing Edmonton with canvassing territory overlays and support level legend -
Public Map — interactive Leaflet map with territory cuts, marker clustering, and support levels
-
-
-
-
-
🌍
-

Interactive Map

-
-

Leaflet-powered map with multi-provider geocoding, color-coded markers, cuts overlay, and fullscreen mode.

-
6 geocode providersLeafletClustering
-
-
-
-
🚶
-

GPS Canvassing

-
-

Full-screen mobile canvass map with real-time GPS, walking route algorithm, visit recording, and outcome tracking.

-
GPS trackingWalking routesMobile-first
-
-
-
-
-

Polygon Cuts

-
-

Draw geographic boundaries on the map. Assign locations to cuts for organized canvassing territories.

-
Drawing modePoint-in-polygonSpatial queries
-
-
-
-
📅
-

Volunteer Shifts

-
-

Shift scheduling with public signup, confirmation emails, cut assignment, and capacity management.

-
Public signupEmail confirmCut assignment
-
-
-
-
📋
-

Walk Sheets

-
-

Printable walk sheet forms with QR codes for each cut. Take the field data offline with printed reports.

-
QR codesPrintableCut reports
-
-
-
-
🇨🇦
-

NAR Import

-
-

Import Canadian National Address Register data with province/city/postal filtering, coordinate projection, and streaming.

-
2025 formatProj4Streaming
-
-
-
-
📆
-

Gancio Events

-
-

Public event calendar synced from shifts via Gancio. OAuth integration, map markers for upcoming events, and embeddable GrapesJS block.

-
OAuth syncMap markersEmbeddable
-
-
-
- - -
-
-
🎬
-
-

Content & Media

-

Video, photos, playlists, page builder, documentation, and web IDE

-
-
-
- Public media gallery with video cards, sidebar navigation, chat, and content categories -
Media Gallery — browse videos, shorts, photos, and playlists with live chat and reactions
-
-
-
-
-
📹
-

Video Library

-
-

Upload and manage videos with FFprobe metadata, scheduled publishing, view analytics, emoji reactions, threaded comments, and live chat.

-
AnalyticsLive chatScheduling
-
-
-
-
🎨
-

Landing Page Builder

-
-

GrapesJS drag-and-drop page editor with block library, custom components, and instant public publishing at /p/slug.

-
Drag & dropBlock libraryInstant publish
-
-
-
-
📖
-

MkDocs Documentation

-
-

Material-themed docs with full-text search, blog, social cards, and Gitea-backed page comments with anonymous posting and moderation.

-
Material themeCommentsBlog
-
-
-
-
💻
-

Code Server

-
-

Full VS Code in the browser. Edit configuration, templates, and code from anywhere without SSH.

-
VS CodeBrowser IDEExtensions
-
-
-
-
-

Excalidraw Whiteboard

-
-

Collaborative diagramming and whiteboard tool. Plan canvassing routes, sketch campaign strategies, and brainstorm as a team.

-
CollaborativeDiagramsReal-time
-
-
-
-
📷
-

Photo Management

-
-

Album organization with bulk uploads, metadata extraction, and engagement tracking. Reactions, comments, and a public photo gallery.

-
AlbumsEngagementGallery
-
-
-
-
🎵
-

Playlists

-
-

Curated video collections with admin, user, and public playlists. Drag-reorder, sidebar navigation, featured carousel, and dedicated viewer page.

-
CuratedPublic/PrivateReorderable
-
-
-
-
📲
-

Shorts Feed

-
-

TikTok-style vertical video feed for clips under 60 seconds. Autoplay, sorting modes, and mobile-optimized swipeable interface.

-
Vertical videoAutoplayMobile-first
-
-
-
- - -
-
-
📊
-
-

Data & Automation

-

Database browsing, workflow automation, version control, search, and utilities

-
-
-
- Public homepage with hero section, upcoming shifts, latest videos, and recent activity feed -
Public Homepage — hero section, upcoming shifts, latest videos, and activity feed
-
-
-
-
-
🗄
-

NocoDB

-
-

Airtable-alternative database browser. Browse, filter, and export your campaign data through a spreadsheet-like interface.

-
Read-onlyFiltersExport
-
-
-
-
-

n8n Workflows

-
-

Visual workflow automation. Connect APIs, trigger actions, and build custom integrations without code.

-
Visual builder400+ integrationsWebhooks
-
-
-
-
📦
-

Gitea

-
-

Self-hosted Git repository. Version control for your campaign code, configs, and documentation.

-
Git hostingIssuesCI/CD
-
-
-
-
📱
-

Mini QR

-
-

QR code generator for walk sheets, campaign materials, and event signage. Instant PNG generation.

-
PNG outputEmbeddablePublic API
-
-
-
-
-

Command Palette

-
-

Global Ctrl+K search across pages, campaigns, locations, users, and settings. Fuzzy matching, recent items, and keyboard-driven navigation.

-
Ctrl+KFuzzy searchKeyboard-first
-
-
-
-
-

Navigation Settings

-
-

Customizable public nav menu with feature toggles, custom external links, drag-reorder, and real-time preview. Control what visitors see.

-
Drag-reorderFeature flagsCustom links
-
-
-
- - -
-
-
🛡
-
-

DevOps & Security

-

Tunnel management, monitoring, security hardening, and backups

-
-
-
- Observability dashboard with Prometheus metrics, Grafana dashboards, and service health -
Observability Dashboard — Prometheus metrics, Grafana dashboards, and alert management
-
-
-
-
-
🌐
-

Pangolin Tunnel

-
-

Expose your self-hosted services to the internet without port forwarding. Newt container integration with automatic SSL.

-
No port-forwardAuto SSLNewt
-
-
-
-
📈
-

Prometheus + Grafana

-
-

12 custom metrics, 3 dashboards, alert rules, and service health monitoring. Full observability stack.

-
12 metrics3 dashboardsAlerts
-
-
-
-
🔏
-

Security Hardened

-
-

13-finding security audit addressed. JWT rotation, rate limiting, XSS prevention, encrypted secrets, HSTS headers.

-
Audit completeRBACEncryption
-
-
-
-
💾
-

Automated Backups

-
-

PostgreSQL dumps, Listmonk data, uploads archive, and optional S3 upload. One-command backup script.

-
PostgreSQLS3 optionalScripted
-
-
-
-
🔐
-

Vaultwarden

-
-

Self-hosted Bitwarden-compatible password manager. Secure credential sharing for your team with real-time sync and browser extensions.

-
BitwardenTeam sharingEncrypted
-
-
-
-
👥
-

User Provisioning

-
-

Automatic account sync across Rocket.Chat, Gitea, Vaultwarden, and Listmonk. Eager or lazy strategies with per-user status tracking and bulk sync.

-
4 servicesAuto-syncLifecycle hooks
-
-
-
- - -
-
-
💳
-
-

Fundraising & Commerce

-

Donations, subscriptions, product sales, and supporter monetization

-
-
-
- Subscription pricing page with free and pro plan cards, monthly/yearly toggle, and feature lists -
Pricing Plans — subscription tiers with monthly/yearly billing and feature comparison
-
-
-
-
-
💰
-

Donation Platform

-
-

Accept one-time donations with configurable suggested amounts, anonymous giving, and automatic tax receipts via email.

-
StripeAnonymousReceipts
-
-
-
-
🔄
-

Subscription Plans

-
-

Recurring revenue with tiered plans, monthly and yearly billing, and automatic renewal management. Replace Patreon.

-
RecurringTiersMRR tracking
-
-
-
-
🛒
-

Product Shop

-
-

Sell digital products, event tickets, and merchandise. Inventory management, download delivery, and capacity limits.

-
Digital goodsEventsInventory
-
-
-
-
📊
-

Payment Dashboard

-
-

Revenue analytics with subscriber counts, MRR tracking, donation history, and CSV exports for accounting.

-
AnalyticsCSV exportRefunds
-
-
-
-
📢
-

Gallery Ads

-
-

Promote donations, products, and subscriptions within the media gallery. Visibility targeting, scheduling, and click analytics.

-
TargetingSchedulingCTR tracking
-
-
-
-
📝
-

Donation Pages

-
-

Custom branded donation pages with configurable amounts, thank-you messages, and public slugs. Multiple campaigns with independent branding and goals.

-
Custom brandingSlug URLsGoals
-
-
-
- - -
-
- -
-

Social & Community

-

Friendships, activity feeds, achievements, groups, reactions, and real-time notifications

-
-
-
- Wall of Fame with volunteer spotlight, leaderboard tabs for canvass, shifts, and campaigns -
Wall of Fame — volunteer spotlight, achievement leaderboards, and community recognition
-
-
-
-
-
👥
-

Friend System

-
-

Friend requests, suggestions, pokes, cross-module badges on campaigns, shifts, and the map.

-
Friend requestsSuggestionsPoke
-
-
-
-
📰
-

Activity Feed

-
-

Real-time SSE feed of friend activity across campaigns, shifts, canvassing, and responses.

-
Real-timeSSECross-module
-
-
-
-
🏆
-

Achievements & Notifications

-
-

Milestone badges, real-time notification bell with friend requests, pokes, comments, and alerts.

-
BadgesBell UIReal-time
-
-
-
-
👫
-

Groups & Teams

-
-

Auto-groups for shift teams and campaign crews, custom groups, and shared updates.

-
Shift teamsCampaign crewsCustom groups
-
-
-
-
😍
-

Reactions & Comments

-
-

6 emoji reactions with floating animations, threaded comments with word-filter safety, pagination, and auto-notification.

-
6 emoji typesThreadedContent safety
-
-
-
- - -
-
-
🇨🇦
-
-

Data Sovereignty

-

Canadian-built, privacy-first, no foreign surveillance, no lock-in

-
-
-
-
-
-
🏠
-

Canadian Residency

-
-

Built in Edmonton, Alberta. Hosted on Canadian soil. Subject only to Canadian law. No Patriot Act exposure.

-
Alberta-builtCanadian law
-
-
-
-
🔓
-

No Lock-in

-
-

Export everything anytime. Standard PostgreSQL database, standard file formats. Switch away whenever you want.

-
Full exportStandard formats
-
-
-
-
🛡
-

Privacy First

-
-

No analytics tracking your users. No corporate data mining. Your supporters' data protected by architecture, not policy.

-
No trackingBy design
-
-
-
-
👁
-

No Surveillance

-
-

No NSA. No FISA courts. No corporate oversight. Complete operational security for your political organizing.

-
Zero surveillanceOpSec
-
-
-
- -
-
- - -
-
-
-

Living Network

-

Real sites powered by Changemaker Lite in production today

-
- - -
-
-
6+
-
Live Sites
-
-
-
24/7
-
Uptime
-
-
-
$0
-
Monthly Fees
-
-
-
100%
-
Data Ownership
-
-
-
-
- - -
-
-
-

Pricing

-

No hidden fees. No usage limits. No surprises. Self-host Free Forever

-
- -
-
-

Self-Hosted

-
$0
-
forever
-
    -
  • All 45+ campaign tools
  • -
  • Unlimited users & data
  • -
  • Complete documentation
  • -
  • Community support
  • -
  • Your infrastructure
  • -
  • 100% open source
  • -
- Installation Guide -

For tech-savvy campaigns

-
- -
-

Managed Hosting

-
Custom
-
monthly
-
    -
  • We handle everything
  • -
  • Canadian data centres
  • -
  • Daily backups
  • -
  • 24/7 monitoring
  • -
  • Security updates
  • -
  • Priority support
  • -
- Contact Sales -

For larger campaigns

-
-
- -
-

Compare Your Savings

-

Average campaign using corporate tools: $1,200–$4,000/month

-

Same capabilities with Changemaker Lite: $0 (self-hosted)

- See detailed cost breakdown → -
-
-
- - -
- - -
-

Ready to Grow Your Network?

-

Join campaigns using open-source tools to build real political power.

- -
- 30-minute setup - Your data stays yours - No monthly fees -
-
-
- - - - - -
- -
- - - - + + +
+
+
+

Who We Are

+

The Bunker Operations

+
+
+
+
+

BNKops is a collective with a central administrative team. We operate as a co-operative of skilled individuals passionate about building community capacity through ethical technology and organizing strategies.

+

Our group comprises trusted collaborators, comrades, and co-conspirators sharing goals in community growth. Based in amiskwaciy-waskahikan (Edmonton), we are grateful for the land's provisions and strive to honor it through our work.

+
+
+
+
+ +

Co-operative Based

+

Creating resources that are open-source, low-cost, easy to operate, and corporation-free.

+
+
+ +

Community Focused

+

Our group comprises trusted collaborators, comrades, and co-conspirators sharing goals in community growth.

+
+
+ +

Open Source

+

All our tools and resources are open-source, ensuring accessibility and transparency.

+
+
+
+
+ Visit our repository +
+
+
+ +
+
+
+

Work With Us

+

Looking to collaborate? Reach out, let's chat.

+
+
+
+

Contact Us

+
+ + + admin@bnkops.ca + + + + cmlite.org + + + + archive.bnkops.com + +
+ +
+

Administration

+
+

Reed Larsen 💅

+

The Bunker Admin

+ reed@bnkops.ca +
+
+

Shayla Breen 🤔

+

The Bunker Strategist

+ shayla@bnkops.ca +
+
+
+ + +
+

Stay Updated

+

Subscribe to our newsletter to get weekly updates on our latest projects and community initiatives.

+ + Subscribe to Newsletter + +
+
+
+ +
+ + + + - \ No newline at end of file + diff --git a/mkdocs/mkdocs.yml b/mkdocs/mkdocs.yml index 2852fb46..ba2f9036 100644 --- a/mkdocs/mkdocs.yml +++ b/mkdocs/mkdocs.yml @@ -1,6 +1,6 @@ -site_name: Changemaker Lite -site_description: Build Power. Not Rent It. Own your digital infrastructure. -site_url: https://bnkserve.org +site_name: The Bunker Operations +site_description: Community Strategy, Tactics, and Technology. Open-source, self-hosted, corporation-free infrastructure for community organizations. +site_url: https://bnkops.com site_author: Bunker Operations docs_dir: docs site_dir: site @@ -159,93 +159,5 @@ copyright: > # Navigation nav: - Home: index.md - - Docs: - - docs/index.md - - Getting Started: - - docs/getting-started/index.md - - Installation: docs/getting-started/installation.md - - Services Overview: docs/getting-started/services.md - - Environment Variables: docs/getting-started/environment-variables.md - - First Steps: docs/getting-started/first-steps.md - - Updates & Upgrades: docs/getting-started/upgrades.md - - Control Panel (CCP): docs/getting-started/control-panel.md - - Features at a Glance: docs/getting-started/features.md - - Admin Guide: - - docs/admin/index.md - - Dashboard: docs/admin/dashboard.md - - People & Access: docs/admin/people-access.md - - Advocacy: - - docs/admin/advocacy/index.md - - Campaigns: docs/admin/advocacy/campaigns.md - - Responses: docs/admin/advocacy/responses.md - - Representatives: docs/admin/advocacy/representatives.md - - Email Queue: docs/admin/advocacy/email-queue.md - - Broadcast: - - docs/admin/broadcast/index.md - - Newsletter: docs/admin/broadcast/newsletter.md - - Email Templates: docs/admin/broadcast/email-templates.md - - SMS: docs/admin/broadcast/sms.md - - Web Content: - - docs/admin/web/index.md - - Landing Pages: docs/admin/web/landing-pages.md - - Homepage: docs/admin/web/homepage.md - - Navigation: docs/admin/web/navigation.md - - Documentation: docs/admin/web/documentation.md - - Map & Canvassing: - - docs/admin/map/index.md - - Locations: docs/admin/map/locations.md - - Areas: docs/admin/map/areas.md - - Shifts: docs/admin/map/shifts.md - - Canvassing: docs/admin/map/canvassing.md - - Data Quality: docs/admin/map/data-quality.md - - Map Settings: docs/admin/map/settings.md - - Media: - - docs/admin/media/index.md - - Library: docs/admin/media/library.md - - Analytics: docs/admin/media/analytics.md - - Curated Gallery: docs/admin/media/curated.md - - Moderation: docs/admin/media/moderation.md - - Gallery Ads: docs/admin/media/ads.md - - Payments: - - docs/admin/payments/index.md - - Products: docs/admin/payments/products.md - - Donations: docs/admin/payments/donations.md - - Plans: docs/admin/payments/plans.md - - Settings: docs/admin/payments/settings.md - - Services: - - docs/admin/services/index.md - - Tunnel: docs/admin/services/tunnel.md - - CrowdSec & Security: docs/admin/services/crowdsec.md - - Monitoring: docs/admin/services/monitoring.md - - Integrations: docs/admin/services/integrations.md - - User Provisioning: docs/admin/services/user-provisioning.md - - Settings: docs/admin/settings.md - - User Guide: - - docs/user-guide/index.md - - Campaigns: docs/user-guide/campaigns.md - - Map: docs/user-guide/map.md - - Shifts: docs/user-guide/shifts.md - - Events: docs/user-guide/events.md - - Gallery: docs/user-guide/gallery.md - - Shop & Pricing: docs/user-guide/shop.md - - Donations: docs/user-guide/donations.md - - Your Profile: docs/user-guide/profile.md - - Volunteer Guide: - - docs/volunteer/index.md - - Canvassing: docs/volunteer/canvassing.md - - Shifts: docs/volunteer/shifts.md - - Social: docs/volunteer/social.md - - Achievements: docs/volunteer/achievements.md - - Deployment: - - docs/deployment/index.md - - Architecture: - - docs/architecture/index.md - - Services: - - docs/services/index.md - - API Reference: - - docs/api/index.md - - Troubleshooting: - - docs/troubleshooting/index.md - - Philosophy: docs/phil.md - Blog: - blog/index.md diff --git a/nginx/conf.d/services.conf.template b/nginx/conf.d/services.conf.template index 9e530917..90c84953 100644 --- a/nginx/conf.d/services.conf.template +++ b/nginx/conf.d/services.conf.template @@ -1,7 +1,22 @@ +# Archive — preserved documentation from all versions +server { + listen 80; + server_name archive.${DOMAIN}; + + location / { + set $upstream_archive http://archive-bnkops:80; + proxy_pass $upstream_archive; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} + # Gitea — allows iframe embedding from admin (app.${DOMAIN}) server { listen 80; - server_name git.${DOMAIN}; + server_name gitea.${DOMAIN}; add_header Content-Security-Policy "frame-ancestors 'self' app.${DOMAIN}" always; # Increase max body size for large git pushes (2GB)