812 lines
23 KiB
Markdown
Executable File
812 lines
23 KiB
Markdown
Executable File
# Example Political Apps
|
|
In addition to Change Maker, bnkops has already completed development on plugins for newsletters, email targeting, and micro sites. These technologies are also highly customizable, free to deploy, and actively maintained by bnkops.
|
|
|
|
bnkops will bake these plugins directly into the installation process for Change Maker (V4 here we come). For now, manuals for installation, hosting, and deployment can be found at [repo.bnkops.com](https://repo.bnkops.com/Free%20Office%20Software%20Stack%20%F0%9F%A4%AF/The%20Bunker%20Ops%20Server%20Build-Out.html).
|
|
|
|
# Email Button
|
|
|
|
Insert a counter button to email local council members in Edmonton. Example:
|
|
|
|
<!-- Begin Edmonton Council Emailer -->
|
|
<div id="edmonton-council-emailer" class="ece-container">
|
|
<style>
|
|
#edmonton-council-emailer,
|
|
#edmonton-council-emailer * {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
font-family: inherit;
|
|
}
|
|
|
|
#edmonton-council-emailer.ece-container {
|
|
position: relative;
|
|
width: 100%;
|
|
max-width: 1200px;
|
|
margin: 20px auto;
|
|
padding: 30px;
|
|
font-family: Arial, sans-serif;
|
|
font-size: 16px;
|
|
line-height: 1.5;
|
|
color: #fff;
|
|
background: #1e2124;
|
|
border-radius: 12px;
|
|
isolation: isolate;
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
#edmonton-council-emailer .ece-button-container {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 20px;
|
|
background: #2a2d31;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
#edmonton-council-emailer .ece-button {
|
|
padding: 15px 30px;
|
|
font-size: 18px;
|
|
background-color: #ffc107;
|
|
color: #000;
|
|
border: none;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
font-weight: bold;
|
|
text-transform: uppercase;
|
|
letter-spacing: 1px;
|
|
}
|
|
|
|
#edmonton-council-emailer .ece-button:hover {
|
|
background-color: #ffcd38;
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(255, 193, 7, 0.3);
|
|
}
|
|
|
|
#edmonton-council-emailer .ece-counter {
|
|
font-size: 24px;
|
|
font-weight: bold;
|
|
color: #ffc107;
|
|
transition: all 0.3s ease;
|
|
padding: 10px 20px;
|
|
border-radius: 8px;
|
|
background: #2a2d31;
|
|
border: 2px solid #ffc107;
|
|
min-width: 200px;
|
|
text-align: center;
|
|
}
|
|
|
|
#edmonton-council-emailer .ece-counter.animate {
|
|
transform: scale(1.1);
|
|
background: #ffc107;
|
|
color: #000;
|
|
}
|
|
|
|
#edmonton-council-emailer .ece-modal {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: rgba(0,0,0,0.8);
|
|
z-index: 999999;
|
|
}
|
|
|
|
#edmonton-council-emailer .ece-modal-content {
|
|
position: relative;
|
|
background-color: #1e2124;
|
|
margin: 5% auto;
|
|
padding: 30px;
|
|
width: 80%;
|
|
max-width: 1200px;
|
|
max-height: 80vh;
|
|
overflow-y: auto;
|
|
border-radius: 12px;
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
|
border: 1px solid #ffc107;
|
|
}
|
|
|
|
#edmonton-council-emailer .ece-close {
|
|
position: absolute;
|
|
right: 20px;
|
|
top: 20px;
|
|
font-size: 28px;
|
|
cursor: pointer;
|
|
color: #ffc107;
|
|
text-decoration: none;
|
|
line-height: 1;
|
|
transition: all 0.3s ease;
|
|
width: 40px;
|
|
height: 40px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: 50%;
|
|
background: #2a2d31;
|
|
}
|
|
|
|
#edmonton-council-emailer .ece-close:hover {
|
|
background: #ffc107;
|
|
color: #000;
|
|
transform: rotate(90deg);
|
|
}
|
|
|
|
#edmonton-council-emailer .ece-councillor-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
gap: 20px;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
#edmonton-council-emailer .ece-councillor-card {
|
|
border: 1px solid #2a2d31;
|
|
border-radius: 12px;
|
|
padding: 15px;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
background-color: #2a2d31;
|
|
}
|
|
|
|
#edmonton-council-emailer .ece-councillor-card:hover {
|
|
transform: translateY(-5px);
|
|
box-shadow: 0 8px 24px rgba(255, 193, 7, 0.2);
|
|
border-color: #ffc107;
|
|
}
|
|
|
|
#edmonton-council-emailer .ece-councillor-card img {
|
|
width: 100%;
|
|
height: 200px;
|
|
object-fit: cover;
|
|
border-radius: 8px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
#edmonton-council-emailer .ece-councillor-card:hover img {
|
|
transform: scale(1.02);
|
|
}
|
|
|
|
#edmonton-council-emailer .ece-councillor-card h3 {
|
|
margin: 15px 0 5px;
|
|
color: #fff;
|
|
font-size: 1.2em;
|
|
font-weight: bold;
|
|
}
|
|
|
|
#edmonton-council-emailer .ece-councillor-card p {
|
|
margin: 5px 0;
|
|
color: #ffc107;
|
|
font-size: 1em;
|
|
}
|
|
|
|
#edmonton-council-emailer .ece-modal h2 {
|
|
color: #ffc107;
|
|
font-size: 2em;
|
|
margin-bottom: 30px;
|
|
font-weight: bold;
|
|
text-align: center;
|
|
}
|
|
|
|
@keyframes countUp {
|
|
from {
|
|
transform: translateY(20px);
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
transform: translateY(0);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<div class="ece-button-container">
|
|
<button class="ece-button" onclick="ECE.openModal()">Email Your Councillor</button>
|
|
<div class="ece-counter" id="ece-counter">0 emails sent</div>
|
|
</div>
|
|
|
|
<div class="ece-modal" id="ece-modal">
|
|
<div class="ece-modal-content">
|
|
<span class="ece-close" onclick="ECE.closeModal()">×</span>
|
|
<h2>Select Your City Councillor</h2>
|
|
<div class="ece-councillor-grid" id="ece-councillor-grid"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const ECE = {
|
|
count: 0,
|
|
councillors: [
|
|
{
|
|
name: "Mayor Amarjeet Sohi",
|
|
title: "City-Wide Mayor",
|
|
email: "mayorsoffice@edmonton.ca",
|
|
image: "https://www.edmonton.ca/sites/default/files/public-files/feature-images/Mayor-Sohi-800x494.jpg",
|
|
ward: "City-Wide"
|
|
},
|
|
{
|
|
name: "Councillor Erin Rutherford",
|
|
title: "Ward Anirniq Councillor",
|
|
email: "erin.rutherford@edmonton.ca",
|
|
image: "https://www.edmonton.ca/sites/default/files/public-files/feature-images/Anirniq-councillor_800x494.jpg",
|
|
ward: "Anirniq"
|
|
},
|
|
{
|
|
name: "Councillor Aaron Paquette",
|
|
title: "Ward Dene Councillor",
|
|
email: "aaron.paquette@edmonton.ca",
|
|
image: "https://www.edmonton.ca/sites/default/files/public-files/feature-images/Dene-councillor_800x494.jpg",
|
|
ward: "Dene"
|
|
},
|
|
{
|
|
name: "Councillor Jennifer Rice",
|
|
title: "Ward Ipiihkoohkanipiaohtsi Councillor",
|
|
email: "jennifer.rice@edmonton.ca",
|
|
image: "https://www.edmonton.ca/sites/default/files/public-files/feature-images/Ipiihkoohkanipiaohtsi-councillor_800x494.jpg",
|
|
ward: "Ipiihkoohkanipiaohtsi"
|
|
},
|
|
{
|
|
name: "Councillor Keren Tang",
|
|
title: "Ward Karhiio Councillor",
|
|
email: "keren.tang@edmonton.ca",
|
|
image: "https://www.edmonton.ca/sites/default/files/public-files/feature-images/Karhiio-councillor_800x494.jpg",
|
|
ward: "Karhiio"
|
|
},
|
|
{
|
|
name: "Councillor Ashley Salvador",
|
|
title: "Ward Métis Councillor",
|
|
email: "ashley.salvador@edmonton.ca",
|
|
image: "https://www.edmonton.ca/sites/default/files/public-files/feature-images/Métis-councillor_800x494.jpg",
|
|
ward: "Métis"
|
|
},
|
|
{
|
|
name: "Councillor Andrew Knack",
|
|
title: "Ward Nakota Isga Councillor",
|
|
email: "andrew.knack@edmonton.ca",
|
|
image: "https://www.edmonton.ca/sites/default/files/public-files/feature-images/Nakota-Isga-councillor_800x494.jpg",
|
|
ward: "Nakota Isga"
|
|
},
|
|
{
|
|
name: "Councillor Anne Stevenson",
|
|
title: "Ward O-day'min Councillor",
|
|
email: "anne.stevenson@edmonton.ca",
|
|
image: "https://www.edmonton.ca/sites/default/files/public-files/feature-images/O-day'min-councillor_800x494.jpg",
|
|
ward: "O-day'min"
|
|
},
|
|
{
|
|
name: "Councillor Michael Janz",
|
|
title: "Ward papastew Councillor",
|
|
email: "michael.janz@edmonton.ca",
|
|
image: "https://www.edmonton.ca/sites/default/files/public-files/feature-images/papastew-councillor_800x494.jpg",
|
|
ward: "papastew"
|
|
},
|
|
{
|
|
name: "Councillor Tim Cartmell",
|
|
title: "Ward pihêsiwin Councillor",
|
|
email: "tim.cartmell@edmonton.ca",
|
|
image: "https://www.edmonton.ca/sites/default/files/public-files/feature-images/pihêsiwin-councillor_800x494.jpg",
|
|
ward: "pihêsiwin"
|
|
},
|
|
{
|
|
name: "Councillor Sarah Hamilton",
|
|
title: "Ward sipiwiyiniwak Councillor",
|
|
email: "sarah.hamilton@edmonton.ca",
|
|
image: "https://www.edmonton.ca/sites/default/files/public-files/feature-images/sipiwiyiniwak-councillor_800x494.jpg",
|
|
ward: "sipiwiyiniwak"
|
|
},
|
|
{
|
|
name: "Councillor Jo-Anne Wright",
|
|
title: "Ward Sspomitapi Councillor",
|
|
email: "jo-anne.wright@edmonton.ca",
|
|
image: "https://www.edmonton.ca/sites/default/files/public-files/feature-images/Sspomitapi-councillor_800x494.jpg",
|
|
ward: "Sspomitapi"
|
|
},
|
|
{
|
|
name: "Councillor Karen Principe",
|
|
title: "Ward tastawiyiniwak Councillor",
|
|
email: "karen.principe@edmonton.ca",
|
|
image: "https://www.edmonton.ca/sites/default/files/public-files/feature-images/tastawiyiniwak-councillor_800x494.jpg",
|
|
ward: "tastawiyiniwak"
|
|
}
|
|
],
|
|
|
|
init: function() {
|
|
if (window.ECEInitialized) return;
|
|
window.ECEInitialized = true;
|
|
|
|
document.getElementById('ece-modal').addEventListener('click', function(event) {
|
|
if (event.target === this) {
|
|
ECE.closeModal();
|
|
}
|
|
});
|
|
},
|
|
|
|
createCouncillorCards: function() {
|
|
const grid = document.getElementById('ece-councillor-grid');
|
|
if (!grid) return;
|
|
|
|
this.councillors.forEach(councillor => {
|
|
const card = document.createElement('div');
|
|
card.className = 'ece-councillor-card';
|
|
card.innerHTML = `
|
|
<img src="${councillor.image}" alt="${councillor.name}">
|
|
<h3>${councillor.name}</h3>
|
|
<p>${councillor.ward}</p>
|
|
`;
|
|
card.onclick = () => this.emailCouncillor(councillor);
|
|
grid.appendChild(card);
|
|
});
|
|
},
|
|
|
|
openModal: function() {
|
|
const modal = document.getElementById('ece-modal');
|
|
if (!modal) return;
|
|
|
|
modal.style.display = 'block';
|
|
if (!document.getElementById('ece-councillor-grid').children.length) {
|
|
this.createCouncillorCards();
|
|
}
|
|
},
|
|
|
|
closeModal: function() {
|
|
const modal = document.getElementById('ece-modal');
|
|
if (modal) modal.style.display = 'none';
|
|
},
|
|
|
|
animateCount: function(from, to) {
|
|
const counter = document.getElementById('ece-counter');
|
|
const duration = 1000; // 1 second animation
|
|
const steps = 20;
|
|
const increment = (to - from) / steps;
|
|
let current = from;
|
|
let step = 0;
|
|
|
|
counter.classList.add('animate');
|
|
|
|
const interval = setInterval(() => {
|
|
current += increment;
|
|
step++;
|
|
|
|
if (step >= steps) {
|
|
clearInterval(interval);
|
|
current = to;
|
|
setTimeout(() => {
|
|
counter.classList.remove('animate');
|
|
}, 300);
|
|
}
|
|
|
|
counter.textContent = `${Math.round(current)} emails sent`;
|
|
}, duration / steps);
|
|
},
|
|
|
|
emailCouncillor: function(councillor) {
|
|
const oldCount = this.count;
|
|
this.count++;
|
|
this.animateCount(oldCount, this.count);
|
|
this.closeModal();
|
|
|
|
const subject = "Constituent Feedback - [Your Issue]";
|
|
const body = `Dear ${councillor.name},
|
|
|
|
I am a constituent in ${councillor.ward} and I am writing to you regarding [describe your issue].
|
|
|
|
[Describe how this issue affects you and your community]
|
|
|
|
I would appreciate if you could [describe your requested action].
|
|
|
|
Thank you for your time and consideration.
|
|
|
|
Sincerely,
|
|
[Your Name]
|
|
[Your Address]
|
|
[Your Phone Number]`;
|
|
|
|
const encodedSubject = encodeURIComponent(subject);
|
|
const encodedBody = encodeURIComponent(body);
|
|
|
|
window.location.href = `mailto:${councillor.email}?subject=${encodedSubject}&body=${encodedBody}`;
|
|
}
|
|
};
|
|
|
|
// Initialize the component when the document is ready
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
ECE.init();
|
|
});
|
|
</script>
|
|
</div>
|
|
<!-- End Edmonton Council Emailer -->
|
|
|
|
[See Code Snippets For More](Code Snippets.md){ .md-button }
|
|
|
|
# Embed Any iframe
|
|
Any iframe embedding will work and render on site allowing for adding audio/visuals from thousands of different sources. For example:
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<style>
|
|
.playlist-container {
|
|
background: linear-gradient(135deg, #1a1a1a, #2d2d2d);
|
|
border-radius: 16px;
|
|
padding: 2rem;
|
|
color: #e0e0e0;
|
|
font-family: system-ui, -apple-system, sans-serif;
|
|
margin: 1rem 0;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.playlist-frame {
|
|
position: relative;
|
|
border-radius: 12px;
|
|
overflow: hidden;
|
|
background: rgba(255, 255, 255, 0.05);
|
|
padding: 1px;
|
|
z-index: 1;
|
|
}
|
|
|
|
.playlist-frame::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 2px;
|
|
background: linear-gradient(90deg, transparent, #FFD700, transparent);
|
|
animation: shimmer 3s infinite;
|
|
}
|
|
|
|
@keyframes shimmer {
|
|
0% { transform: translateX(-100%); }
|
|
100% { transform: translateX(100%); }
|
|
}
|
|
|
|
.playlist-quote {
|
|
text-align: center;
|
|
margin-top: 1.5rem;
|
|
font-style: italic;
|
|
color: #FFD700;
|
|
font-size: 1.2rem;
|
|
opacity: 0;
|
|
transform: translateY(10px);
|
|
animation: fadeIn 0.5s ease forwards 0.5s;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.musical-note {
|
|
display: inline-block;
|
|
animation: float 3s ease-in-out infinite;
|
|
}
|
|
|
|
.musical-note:nth-child(2) {
|
|
animation-delay: 0.2s;
|
|
}
|
|
|
|
.musical-note:nth-child(3) {
|
|
animation-delay: 0.4s;
|
|
}
|
|
|
|
@keyframes float {
|
|
0%, 100% { transform: translateY(0) rotate(0deg); }
|
|
50% { transform: translateY(-10px) rotate(5deg); }
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.magical-sparkle {
|
|
position: absolute;
|
|
pointer-events: none;
|
|
animation: sparkle 2s linear infinite;
|
|
color: #FFD700;
|
|
opacity: 0;
|
|
font-size: 1.2rem;
|
|
}
|
|
|
|
@keyframes sparkle {
|
|
0% { transform: translate(0, 0) rotate(0deg); opacity: 0; }
|
|
50% { opacity: 1; }
|
|
100% { transform: translate(var(--tx), var(--ty)) rotate(360deg); opacity: 0; }
|
|
}
|
|
|
|
.musical-decoration {
|
|
position: absolute;
|
|
font-size: 2rem;
|
|
opacity: 0.15;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.top-left-music {
|
|
top: 10px;
|
|
left: 10px;
|
|
animation: pulse 3s ease-in-out infinite;
|
|
}
|
|
|
|
.bottom-right-music {
|
|
bottom: 10px;
|
|
right: 10px;
|
|
animation: pulse 3s ease-in-out infinite 1.5s;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { transform: scale(1); opacity: 0.15; }
|
|
50% { transform: scale(1.1); opacity: 0.3; }
|
|
}
|
|
|
|
@media (max-width: 600px) {
|
|
.playlist-container {
|
|
padding: 1rem;
|
|
}
|
|
|
|
.playlist-quote {
|
|
font-size: 1rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.musical-decoration {
|
|
font-size: 1.5rem;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="playlist-container">
|
|
<div class="musical-decoration top-left-music">🎼</div>
|
|
<div class="musical-decoration bottom-right-music">🎵</div>
|
|
|
|
<div class="playlist-frame">
|
|
<iframe style="border-radius:12px"
|
|
src="https://open.spotify.com/embed/playlist/5ZY7xLj2TqX5DUrMW89zfq?utm_source=generator&theme=0"
|
|
width="100%"
|
|
height="352"
|
|
frameBorder="0"
|
|
allowfullscreen=""
|
|
allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
|
|
loading="lazy">
|
|
</iframe>
|
|
</div>
|
|
|
|
<div class="playlist-quote">
|
|
<span class="musical-note">🎵</span>
|
|
The revolution will be sung before it is written
|
|
<span class="musical-note">✨</span>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function createSparkle() {
|
|
const container = document.querySelector('.playlist-container');
|
|
const sparkle = document.createElement('div');
|
|
sparkle.className = 'magical-sparkle';
|
|
sparkle.textContent = ['✨', '🎵', '🎶'][Math.floor(Math.random() * 3)];
|
|
|
|
const x = Math.random() * container.offsetWidth;
|
|
const y = Math.random() * container.offsetHeight;
|
|
const tx = (Math.random() - 0.5) * 100;
|
|
const ty = -100 - Math.random() * 50;
|
|
|
|
sparkle.style.left = `${x}px`;
|
|
sparkle.style.top = `${y}px`;
|
|
sparkle.style.setProperty('--tx', `${tx}px`);
|
|
sparkle.style.setProperty('--ty', `${ty}px`);
|
|
|
|
container.appendChild(sparkle);
|
|
|
|
setTimeout(() => sparkle.remove(), 2000);
|
|
}
|
|
|
|
// Create sparkles periodically
|
|
setInterval(createSparkle, 500);
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|
|
## Newsletter
|
|
bnkops uses [listmonk](https://listmonk.app/) for its newsletter management. An example deployment:
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<style>
|
|
.newsletter-container {
|
|
background: linear-gradient(135deg, #1a1a1a, #2d2d2d);
|
|
border-radius: 16px;
|
|
padding: 2rem;
|
|
color: #e0e0e0;
|
|
font-family: system-ui, -apple-system, sans-serif;
|
|
width: 100%;
|
|
max-width: 600px;
|
|
margin: 2rem auto;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.newsletter-container::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 2px;
|
|
background: linear-gradient(90deg, transparent, #FFD700, transparent);
|
|
animation: shimmer 3s infinite;
|
|
}
|
|
|
|
@keyframes shimmer {
|
|
0% { transform: translateX(-100%); }
|
|
100% { transform: translateX(100%); }
|
|
}
|
|
|
|
.form-title {
|
|
color: #FFD700;
|
|
font-size: 1.5rem;
|
|
margin-bottom: 1.5rem;
|
|
text-align: center;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.form-title-icon {
|
|
font-size: 1.8rem;
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.form-label {
|
|
display: block;
|
|
margin-bottom: 0.5rem;
|
|
color: #FFD700;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.form-input {
|
|
width: 100%;
|
|
padding: 0.8rem;
|
|
border: 1px solid rgba(255, 215, 0, 0.3);
|
|
border-radius: 8px;
|
|
background: rgba(255, 255, 255, 0.05);
|
|
color: #fff;
|
|
font-size: 1rem;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.form-input:focus {
|
|
outline: none;
|
|
border-color: #FFD700;
|
|
box-shadow: 0 0 0 2px rgba(255, 215, 0, 0.2);
|
|
}
|
|
|
|
.form-input::placeholder {
|
|
color: rgba(255, 255, 255, 0.5);
|
|
}
|
|
|
|
.checkbox-group {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
margin: 1rem 0;
|
|
}
|
|
|
|
.checkbox-input {
|
|
appearance: none;
|
|
width: 20px;
|
|
height: 20px;
|
|
border: 2px solid rgba(255, 215, 0, 0.3);
|
|
border-radius: 4px;
|
|
background: rgba(255, 255, 255, 0.05);
|
|
cursor: pointer;
|
|
position: relative;
|
|
}
|
|
|
|
.checkbox-input:checked::before {
|
|
content: '✓';
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
color: #FFD700;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.checkbox-label {
|
|
color: #e0e0e0;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.submit-button {
|
|
width: 100%;
|
|
padding: 1rem;
|
|
background: #FFD700;
|
|
color: #1a1a1a;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.submit-button:hover {
|
|
background: #ffd900;
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
.newsletter-container {
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.form-title {
|
|
font-size: 1.3rem;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="newsletter-container">
|
|
<h3 class="form-title">
|
|
<span class="form-title-icon">📫</span>
|
|
Subscribe for Updates
|
|
</h3>
|
|
|
|
<form method="post" action="https://listmonk.bnkops.com/subscription/form">
|
|
<input type="hidden" name="nonce" />
|
|
|
|
<div class="form-group">
|
|
<label class="form-label" for="email">Email Address</label>
|
|
<input
|
|
type="email"
|
|
id="email"
|
|
name="email"
|
|
class="form-input"
|
|
placeholder="your@email.com"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label" for="name">Name (Optional)</label>
|
|
<input
|
|
type="text"
|
|
id="name"
|
|
name="name"
|
|
class="form-input"
|
|
placeholder="Your name"
|
|
/>
|
|
</div>
|
|
|
|
<div class="checkbox-group">
|
|
<input
|
|
type="checkbox"
|
|
id="updates"
|
|
name="l"
|
|
class="checkbox-input"
|
|
checked
|
|
value="038eb469-e141-435d-86eb-2ab4df20cf9c"
|
|
/>
|
|
<label class="checkbox-label" for="updates">
|
|
Periodic Updates (~1 Weekly)
|
|
</label>
|
|
</div>
|
|
|
|
<button type="submit" class="submit-button">
|
|
Subscribe
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
|
|
## Simple Target
|
|
Simple Target is a bnkops app that facilitates a email campaign pointed at a inbox. These can be embedded with ease.
|
|
|
|
<iframe width="800" height="2000" frameborder="0" allow="clipboard-write;camera;geolocation;fullscreen" src="https://budibase.bnkops.com/embed/simple-target-latest"></iframe> |