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 @@ - +
-- 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. -
- - - -Traditional campaign tools weren't built for the reality of political organizing
-Voters ask tough questions. Your team fumbles through PDFs, emails, and scattered Google Docs while the voter loses interest.
-Walk lists in one app, voter info in another, campaign policies somewhere else. Nothing talks to each other.
-$100 here, $500 there. Thousands monthly on tools that don't work together and hold your data hostage.
-Your strategies in corporate clouds. Your movement's future in someone else's hands. Export? Good luck.
-Desktop-first tools that barely work on phones. Canvassers struggling with tiny text and broken interfaces in the field.
-US companies with US regulations. Your Canadian campaign data subject to foreign laws and surveillance.
-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.
-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.
-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.
-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.
-50+ tools connected — each node strengthens the whole
-Email campaigns, SMS outreach, newsletters, advocacy, and team chat
-
- Full newsletter platform with subscriber management, templates, and analytics. Drop-in replacement for Mailchimp.
- -Postal code to representative lookup. Automated advocacy emails to elected officials with tracking and response collection.
- -GrapesJS visual email editor with variable substitution, versioning, and instant preview. Build once, send everywhere.
- -Public response collection with moderation, upvoting, and verification. Showcase supporter voices on your campaigns.
- -Self-hosted team chat with SSO integration. Automatic channel notifications for shift signups, canvass sessions, and campaign responses.
- -Async notification queue for admin alerts and volunteer feedback. Shift reminders, session summaries, and signup confirmations.
- -Text message outreach via Termux Android bridge. Contact lists, templated campaigns, delivery tracking, response sync, and device health monitoring.
- -Floating Rocket.Chat panel for logged-in team members. Minimizable FAB, auth-gated access, and settings-toggleable visibility across the admin interface.
- -GPS tracking, door-to-door canvassing, geographic organization
-
- Leaflet-powered map with multi-provider geocoding, color-coded markers, cuts overlay, and fullscreen mode.
- -Full-screen mobile canvass map with real-time GPS, walking route algorithm, visit recording, and outcome tracking.
- -Draw geographic boundaries on the map. Assign locations to cuts for organized canvassing territories.
- -Shift scheduling with public signup, confirmation emails, cut assignment, and capacity management.
- -Printable walk sheet forms with QR codes for each cut. Take the field data offline with printed reports.
- -Import Canadian National Address Register data with province/city/postal filtering, coordinate projection, and streaming.
- -Public event calendar synced from shifts via Gancio. OAuth integration, map markers for upcoming events, and embeddable GrapesJS block.
- -Video, photos, playlists, page builder, documentation, and web IDE
-
- Upload and manage videos with FFprobe metadata, scheduled publishing, view analytics, emoji reactions, threaded comments, and live chat.
- -GrapesJS drag-and-drop page editor with block library, custom components, and instant public publishing at /p/slug.
- -Material-themed docs with full-text search, blog, social cards, and Gitea-backed page comments with anonymous posting and moderation.
- -Full VS Code in the browser. Edit configuration, templates, and code from anywhere without SSH.
- -Collaborative diagramming and whiteboard tool. Plan canvassing routes, sketch campaign strategies, and brainstorm as a team.
- -Album organization with bulk uploads, metadata extraction, and engagement tracking. Reactions, comments, and a public photo gallery.
- -Curated video collections with admin, user, and public playlists. Drag-reorder, sidebar navigation, featured carousel, and dedicated viewer page.
- -TikTok-style vertical video feed for clips under 60 seconds. Autoplay, sorting modes, and mobile-optimized swipeable interface.
- -Database browsing, workflow automation, version control, search, and utilities
-
- Airtable-alternative database browser. Browse, filter, and export your campaign data through a spreadsheet-like interface.
- -Visual workflow automation. Connect APIs, trigger actions, and build custom integrations without code.
- -Self-hosted Git repository. Version control for your campaign code, configs, and documentation.
- -QR code generator for walk sheets, campaign materials, and event signage. Instant PNG generation.
- -Global Ctrl+K search across pages, campaigns, locations, users, and settings. Fuzzy matching, recent items, and keyboard-driven navigation.
- -Customizable public nav menu with feature toggles, custom external links, drag-reorder, and real-time preview. Control what visitors see.
- -Tunnel management, monitoring, security hardening, and backups
-
- Expose your self-hosted services to the internet without port forwarding. Newt container integration with automatic SSL.
- -12 custom metrics, 3 dashboards, alert rules, and service health monitoring. Full observability stack.
- -13-finding security audit addressed. JWT rotation, rate limiting, XSS prevention, encrypted secrets, HSTS headers.
- -PostgreSQL dumps, Listmonk data, uploads archive, and optional S3 upload. One-command backup script.
- -Self-hosted Bitwarden-compatible password manager. Secure credential sharing for your team with real-time sync and browser extensions.
- -Automatic account sync across Rocket.Chat, Gitea, Vaultwarden, and Listmonk. Eager or lazy strategies with per-user status tracking and bulk sync.
- -Donations, subscriptions, product sales, and supporter monetization
-
- Accept one-time donations with configurable suggested amounts, anonymous giving, and automatic tax receipts via email.
- -Recurring revenue with tiered plans, monthly and yearly billing, and automatic renewal management. Replace Patreon.
- -Sell digital products, event tickets, and merchandise. Inventory management, download delivery, and capacity limits.
- -Revenue analytics with subscriber counts, MRR tracking, donation history, and CSV exports for accounting.
- -Promote donations, products, and subscriptions within the media gallery. Visibility targeting, scheduling, and click analytics.
- -Custom branded donation pages with configurable amounts, thank-you messages, and public slugs. Multiple campaigns with independent branding and goals.
- -Canadian-built, privacy-first, no foreign surveillance, no lock-in
-Built in Edmonton, Alberta. Hosted on Canadian soil. Subject only to Canadian law. No Patriot Act exposure.
- -Export everything anytime. Standard PostgreSQL database, standard file formats. Switch away whenever you want.
- -No analytics tracking your users. No corporate data mining. Your supporters' data protected by architecture, not policy.
- -No NSA. No FISA courts. No corporate oversight. Complete operational security for your political organizing.
- -Real sites powered by Changemaker Lite in production today
-No hidden fees. No usage limits. No surprises. Self-host Free Forever
-For tech-savvy campaigns
-Ready out of the box
-For larger campaigns
-Average campaign using corporate tools: $1,200–$4,000/month
-Same capabilities with Changemaker Lite: $0 (self-hosted)
- See detailed cost breakdown → -Join campaigns using open-source tools to build real political power.
- - -