radical/index.html
Omar Najjar c04c7804c0 xx
2025-03-09 16:45:28 +11:00

2157 lines
No EOL
75 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RADICAL - Australia's Democratic Revolution</title>
<link rel="icon" href="https://radical.omar-c29.workers.dev/memes/favicon.jpg" type="image/png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@300;400;500;600;700&display=swap"
rel="stylesheet">
<style>
:root {
--primary: #ff0099;
--primary-light: #ff66c4;
--primary-dark: #cc007a;
--secondary: #ffffff;
--accent: #ff0099;
--dark: #000000;
--darker: #111111;
--light: #ffffff;
--border: #333333;
--text: #ffffff;
--text-muted: #aaaaaa;
--success: #11cc77;
--danger: #ff0099;
--card-bg: rgba(0, 0, 0, 0.8);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'Roboto Mono', monospace;
}
body {
background-color: var(--dark);
color: var(--text);
line-height: 1.6;
overflow-x: hidden;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 40px 20px;
}
header {
text-align: center;
margin-bottom: 50px;
padding: 40px 20px;
border-radius: 0;
background: var(--card-bg);
border: 1px solid var(--primary);
box-shadow: 0 0 20px rgba(255, 0, 153, 0.3);
}
header h1 {
font-size: 4rem;
font-weight: 700;
letter-spacing: -2px;
margin-bottom: 5px;
text-transform: uppercase;
color: var(--primary);
text-shadow: 0 0 10px rgba(255, 0, 153, 0.5);
}
header p {
font-size: 1.1rem;
font-weight: 400;
margin-top: 10px;
max-width: 600px;
margin-left: auto;
margin-right: auto;
color: var(--text);
}
.tagline {
display: inline-block;
background-color: var(--primary);
padding: 8px 20px;
border-radius: 0;
font-size: 0.9rem;
margin-top: 20px;
letter-spacing: 1px;
text-transform: uppercase;
color: var(--dark);
font-weight: 600;
}
.new-proposal {
background: var(--card-bg);
border-radius: 0;
padding: 30px;
margin-bottom: 40px;
box-shadow: 0 0 20px rgba(255, 0, 153, 0.2);
border: 1px solid var(--border);
transition: all 0.3s ease;
}
.new-proposal:hover {
box-shadow: 0 0 30px rgba(255, 0, 153, 0.3);
border-color: var(--primary);
}
.proposal-input {
width: 100%;
padding: 20px;
border: none;
border-radius: 0;
resize: none;
font-size: 16px;
margin-bottom: 15px;
background-color: rgba(17, 17, 17, 1);
color: var(--text);
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.5);
transition: all 0.3s ease;
border: 1px solid var(--border);
font-family: 'Roboto Mono', monospace;
}
.proposal-input:focus {
outline: none;
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.5), 0 0 0 2px rgba(255, 0, 153, 0.3);
border-color: var(--primary);
}
.proposal-input::placeholder {
color: var(--text-muted);
}
.char-counter {
font-size: 14px;
color: var(--text-muted);
text-align: right;
margin-bottom: 20px;
font-weight: 400;
}
.char-counter.limit {
color: var(--primary);
font-weight: 700;
}
.post-button {
background-color: var(--primary);
color: var(--dark);
border: none;
border-radius: 0;
padding: 12px 30px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
float: right;
text-transform: uppercase;
letter-spacing: 1px;
box-shadow: 0 0 10px rgba(255, 0, 153, 0.3);
transition: all 0.3s ease;
font-family: 'Roboto Mono', monospace;
}
.post-button:hover {
background-color: var(--primary-light);
transform: translateY(-2px);
box-shadow: 0 0 15px rgba(255, 0, 153, 0.4);
}
.post-button:active {
transform: translateY(1px);
box-shadow: 0 0 5px rgba(255, 0, 153, 0.4);
}
.post-button:disabled {
background-color: #333;
color: #555;
cursor: not-allowed;
box-shadow: none;
transform: none;
}
.proposals {
margin-top: 40px;
}
.filter-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
border-bottom: 1px solid var(--border);
padding-bottom: 15px;
}
.filter-title {
font-size: 1.1rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--primary);
}
.sort-select {
padding: 10px 20px;
border-radius: 0;
border: 1px solid var(--border);
background-color: var(--darker);
color: var(--text);
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
font-family: 'Roboto Mono', monospace;
}
.sort-select:hover {
border-color: var(--primary);
}
.sort-select:focus {
outline: none;
box-shadow: 0 0 0 2px rgba(255, 0, 153, 0.3);
}
.proposal-card {
background: var(--card-bg);
border-radius: 0;
padding: 30px;
margin-bottom: 25px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
border: 1px solid var(--border);
transition: all 0.3s ease;
position: relative;
}
.proposal-card:hover {
transform: translateY(-3px);
box-shadow: 0 0 30px rgba(255, 0, 153, 0.2);
border-color: var(--primary);
}
.proposal-author {
font-weight: 600;
margin-bottom: 15px;
font-size: 1rem;
color: var(--primary);
}
.proposal-text {
margin-bottom: 15px;
word-wrap: break-word;
font-size: 1.1rem;
line-height: 1.7;
color: var(--text);
}
.proposal-stats {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
border-top: 1px solid var(--border);
padding-top: 15px;
}
.vote-buttons {
display: flex;
gap: 15px;
}
.vote-button {
background: var(--darker);
border: 1px solid var(--border);
cursor: pointer;
display: flex;
align-items: center;
color: var(--text-muted);
font-size: 14px;
transition: all 0.2s ease;
padding: 6px 14px;
border-radius: 0;
font-family: 'Roboto Mono', monospace;
}
.vote-button:hover {
background-color: var(--darker);
color: var(--text);
border-color: var(--primary);
}
.vote-button.upvoted {
color: var(--dark);
background-color: var(--primary);
border-color: var(--primary);
}
.vote-button.downvoted {
color: var(--primary);
background-color: transparent;
border-color: var(--primary);
}
.vote-icon {
margin-right: 8px;
font-size: 16px;
}
.vote-count {
font-weight: 600;
}
.net-votes {
margin-left: 10px;
font-weight: 600;
}
.net-votes.positive {
color: var(--primary);
}
.net-votes.negative {
color: var(--text-muted);
}
.proposal-time {
color: var(--text-muted);
font-size: 0.85rem;
}
.empty-state {
text-align: center;
padding: 60px 40px;
color: var(--text-muted);
background: var(--card-bg);
border-radius: 0;
border: 1px dashed var(--border);
}
.empty-state p {
font-size: 1.2rem;
margin-bottom: 20px;
}
.power-badge {
position: absolute;
top: 20px;
right: 20px;
background-color: var(--primary);
color: var(--dark);
padding: 5px 12px;
border-radius: 0;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
}
.trending-badge {
background-color: var(--light);
color: var(--dark);
}
footer {
text-align: center;
margin-top: 80px;
padding: 30px 20px;
color: var(--text-muted);
font-size: 14px;
border-top: 1px solid var(--border);
}
.footer-links {
display: flex;
justify-content: center;
gap: 30px;
margin-top: 20px;
}
.footer-links a {
color: var(--primary);
text-decoration: none;
transition: all 0.3s ease;
font-weight: 500;
}
.footer-links a:hover {
color: var(--primary-light);
text-decoration: underline;
}
.loading-indicator {
text-align: center;
padding: 30px;
color: var(--text-muted);
}
.loading-spinner {
display: inline-block;
width: 30px;
height: 30px;
border: 3px solid var(--primary);
border-radius: 50%;
border-top-color: transparent;
animation: spin 1s linear infinite;
margin-bottom: 15px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.error-state {
text-align: center;
padding: 40px;
color: var(--text-muted);
background: var(--card-bg);
border-radius: 0;
border: 1px dashed var(--primary);
}
.error-state p {
font-size: 1.2rem;
margin-bottom: 20px;
color: var(--primary);
}
.retry-button {
background-color: var(--primary);
color: var(--dark);
border: none;
border-radius: 0;
padding: 10px 20px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
text-transform: uppercase;
letter-spacing: 1px;
margin-top: 15px;
font-family: 'Roboto Mono', monospace;
}
/* Petition Modal Styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.85);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
backdrop-filter: blur(4px);
}
.modal-container {
background: var(--card-bg);
width: 90%;
max-width: 500px;
border: 1px solid var(--primary);
box-shadow: 0 0 30px rgba(255, 0, 153, 0.4);
padding: 0;
position: relative;
}
.modal-header {
padding: 20px;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-header h3 {
margin: 0;
color: var(--primary);
font-size: 1.2rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
}
.modal-close {
background: none;
border: none;
color: var(--text-muted);
font-size: 20px;
cursor: pointer;
padding: 0;
margin: 0;
}
.modal-close:hover {
color: var(--primary);
}
.modal-body {
padding: 20px;
}
.modal-body p {
margin-bottom: 20px;
font-size: 0.9rem;
line-height: 1.6;
}
.petition-form {
display: flex;
flex-direction: column;
gap: 15px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 5px;
}
.form-group label {
font-size: 0.8rem;
color: var(--text-muted);
font-weight: 500;
}
.form-input {
padding: 12px;
background-color: var(--darker);
border: 1px solid var(--border);
color: var(--text);
font-size: 14px;
font-family: 'Roboto Mono', monospace;
}
.form-input:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 2px rgba(255, 0, 153, 0.2);
}
.form-error {
color: var(--primary);
font-size: 0.8rem;
margin-top: 5px;
}
.consent-checkbox {
display: flex;
align-items: flex-start;
gap: 10px;
margin-top: 10px;
}
.consent-checkbox input {
margin-top: 2px;
}
.consent-checkbox label {
font-size: 0.8rem;
line-height: 1.4;
color: var(--text-muted);
}
.petition-submit-btn {
background-color: var(--primary);
color: var(--dark);
border: none;
padding: 12px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
text-transform: uppercase;
letter-spacing: 1px;
margin-top: 10px;
transition: all 0.3s ease;
font-family: 'Roboto Mono', monospace;
}
.petition-submit-btn:hover:not([disabled]) {
background-color: var(--primary-light);
transform: translateY(-2px);
}
.petition-submit-btn:disabled {
background-color: #333;
color: #555;
cursor: not-allowed;
transform: none;
}
.petition-badge {
background-color: #11cc77;
color: var(--dark);
}
.petition-info {
padding: 10px 20px;
margin-top: -10px;
margin-bottom: 15px;
font-size: 0.8rem;
display: flex;
align-items: center;
gap: 8px;
background-color: rgba(17, 204, 119, 0.1);
border-top: 1px solid rgba(17, 204, 119, 0.3);
border-bottom: 1px solid rgba(17, 204, 119, 0.3);
}
.petition-icon {
color: #11cc77;
font-weight: bold;
}
.petition-count {
font-weight: 600;
color: #11cc77;
}
.info-text {
font-style: italic;
font-size: 0.7rem;
color: var(--text-muted);
line-height: 1.4;
margin-top: 5px;
border-left: 2px solid var(--border);
padding-left: 10px;
}
/* Meme Upload Styles */
.meme-upload {
margin: 15px 0;
padding: 15px;
border: 1px solid var(--border);
background-color: rgba(0, 0, 0, 0.3);
}
.meme-upload-label {
display: inline-flex;
align-items: center;
gap: 10px;
background-color: var(--darker);
padding: 10px 15px;
border: 1px dashed var(--border);
cursor: pointer;
transition: all 0.3s ease;
width: 100%;
}
.meme-upload-label:hover {
border-color: var(--primary);
background-color: rgba(255, 0, 153, 0.1);
}
.meme-upload-input {
display: none;
}
.meme-upload-icon {
color: var(--primary);
font-size: 1.5rem;
}
.meme-upload-text {
font-size: 0.9rem;
color: var(--text-muted);
}
.meme-preview {
width: 100%;
max-height: 300px;
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
overflow: hidden;
position: relative;
border: 1px solid var(--border);
background-color: var(--darker);
margin-top: 15px;
padding: 10px;
}
.meme-preview img {
max-width: 100%;
max-height: 250px;
object-fit: contain;
margin: 10px 0;
}
.meme-remove {
position: absolute;
top: 10px;
right: 10px;
background-color: rgba(0, 0, 0, 0.7);
color: var(--light);
border: none;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
font-size: 1rem;
transition: all 0.3s ease;
}
.meme-remove:hover {
background-color: var(--primary);
}
.meme-upload-progress {
margin-top: 10px;
height: 6px;
width: 100%;
background-color: var(--darker);
overflow: hidden;
border-radius: 3px;
}
.meme-upload-progress-bar {
height: 100%;
background-color: var(--primary);
width: 0;
transition: width 0.3s ease;
}
.proposal-meme {
margin: 15px 0;
border: 1px solid var(--border);
padding: 10px;
background-color: rgba(0, 0, 0, 0.3);
text-align: center;
}
.proposal-meme img {
max-width: 100%;
max-height: 300px;
object-fit: contain;
display: inline-block;
}
@media (max-width: 600px) {
.container {
padding: 20px;
}
header h1 {
font-size: 2.5rem;
}
header, .new-proposal, .proposal-card {
padding: 25px;
}
.filter-bar {
flex-direction: column;
gap: 15px;
align-items: flex-start;
}
.modal-container {
width: 95%;
max-height: 90vh;
overflow-y: auto;
}
/* Add these styles to your existing CSS */
.share-button {
background: var(--darker);
border: 1px solid var(--border);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-muted);
font-size: 14px;
transition: all 0.2s ease;
padding: 6px 14px;
border-radius: 0;
margin-left: 10px;
}
.share-icon {
display: block;
background-color: transparent !important;
mix-blend-mode: screen; /* This helps with image transparency */
}
/* Optional: Add a tooltip for the share button */
.share-button {
position: relative;
}
.share-button::after {
content: 'Copy link';
position: absolute;
top: -25px;
left: 50%;
transform: translateX(-50%);
background: var(--primary);
color: var(--dark);
padding: 4px 8px;
font-size: 10px;
white-space: nowrap;
opacity: 0;
visibility: hidden;
transition: all 0.2s ease;
}
.share-button:hover::after {
opacity: 1;
visibility: visible;
}
.share-button.copied::after {
content: 'Copied!';
}
/* Styles for the proposal view modal */
.modal-container.proposal-view {
max-width: 600px;
}
.modal-body .proposal-text {
font-size: 1.3rem;
margin: 20px 0;
line-height: 1.7;
}
.share-link-container {
display: flex;
gap: 10px;
margin-top: 15px;
}
.share-link-field {
flex: 1;
padding: 12px;
background-color: var(--darker);
border: 1px solid var(--border);
color: var(--text-muted);
font-size: 14px;
font-family: 'Roboto Mono', monospace;
cursor: text;
}
.share-copy-button {
background-color: var(--primary);
color: var(--dark);
border: none;
padding: 0 15px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
text-transform: uppercase;
letter-spacing: 1px;
transition: all 0.3s ease;
font-family: 'Roboto Mono', monospace;
}
.share-copy-button:hover {
background-color: var(--primary-light);
}
/* Social share buttons */
.social-share-buttons {
display: flex;
gap: 10px;
margin-top: 15px;
}
.social-share-button {
background-color: var(--darker);
color: var(--text);
border: 1px solid var(--border);
padding: 8px 12px;
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
}
.social-share-button:hover {
background-color: var(--primary);
color: var(--dark);
border-color: var(--primary);
}
.social-icon {
font-size: 16px;
}
/* Animation for copying */
@keyframes copyFlash {
0% { background-color: var(--primary); }
100% { background-color: var(--darker); }
}
.flash-animation {
animation: copyFlash 0.5s;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>RADICAL</h1>
<div class="tagline">Vibe, Vote, Veto</div>
<p>Australia's direct democracy movement. Share policy ideas,
vote on proposals, take back control.</p>
</header>
<section class="new-proposal">
<textarea id="proposal-input" class="proposal-input"
placeholder="Your idea to revolutionize Australia? (280 character limit)"
maxlength="280"></textarea>
<div id="char-counter" class="char-counter">280 characters
remaining</div>
<button id="post-button" class="post-button" disabled>Post
Proposal</button>
<div style="clear: both;"></div>
</section>
<section class="proposals">
<div class="filter-bar">
<div class="filter-title">Democracy Goes Brrr</div>
<select id="sort-select" class="sort-select">
<option value="newest">Latest Ideas</option>
<option value="popular">Most liked</option>
<option value="controversial">controversial</option>
<option value="petitions">Petition Progress</option>
</select>
</div>
<div id="proposals-container">
<div class="loading-indicator">
<div class="loading-spinner"></div>
<p>Loading democracy...</p>
</div>
</div>
</section>
<footer>
<p>RADICAL - Direct Democracy Movement 🇦🇺 &copy; 2025</p>
<div class="footer-links">
<a href="#about">About Us</a>
<a href="#join">Join The Movement</a>
<a href="#events">Events</a>
<a href="#contact">Contact</a>
</div>
</footer>
</div>
<script>
// Configuration
const API_BASE_URL = 'https://radical.omar-c29.workers.dev/api'; // Replace with your actual worker URL
// Character limit
const CHAR_LIMIT = 280;
// Global variables
let currentUser;
let verifiedUsers = {};
let currentProposalId = null;
let currentVoteType = null;
// Catchy political slogans for placeholder text
const placeholders = [
"Your petition idea for Australia? (280 character limit)",
"Got a hot take on Australian politics? (280 character limit)",
"Parliament failed the vibe check. Your solution? (280 character limit)",
"What would make Australia actually slap? (280 character limit)",
"Drop your 🔥 policy idea here (280 character limit)"
];
// Document Ready Handler
document.addEventListener('DOMContentLoaded', async () => {
// Initialize variables for DOM elements
initializeApp();
// Initialize meme upload
initMemeUpload();
});
// Initialize application
async function initializeApp() {
// Update UI elements
updateUIText();
// Load user information
await loadUserData();
// Load verified users data
loadVerifiedUsers();
// Setup event listeners
setupEventListeners();
// Initial load of proposals
fetchProposals();
// Check for proposal in URL
checkForProposalInUrl();
}
// Update UI text for petition system
function updateUIText() {
const tagline = document.querySelector('.tagline');
if (tagline) tagline.textContent = 'Petition, Vote, Veto';
const headerDesc = document.querySelector('header p');
if (headerDesc) {
headerDesc.textContent = 'Australia\'s direct democracy movement. Create and sign official petitions, vote on proposals, take back control.';
}
const filterTitle = document.querySelector('.filter-title');
if (filterTitle) filterTitle.textContent = 'Active Petitions';
const sortSelect = document.getElementById('sort-select');
if (sortSelect) {
const options = sortSelect.querySelectorAll('option');
if (options[0]) options[0].textContent = 'Latest ideas';
if (options[1]) options[1].textContent = 'Most liked';
if (options[2]) options[2].textContent = 'controversial';
}
const proposalInput = document.getElementById('proposal-input');
if (proposalInput) {
proposalInput.placeholder = 'Your petition idea for Australia? (280 character limit)';
}
const postButton = document.getElementById('post-button');
if (postButton) {
postButton.textContent = 'Create Petition';
}
}
// Load user data
async function loadUserData() {
// User information (in a real app, this would come from authentication)
currentUser = JSON.parse(localStorage.getItem('radical_user'));
// If no user exists, create a new one
if (!currentUser) {
currentUser = {
id: 'citizen_' + Math.random().toString(36).substr(2, 9),
name: 'Citizen ' + Math.floor(Math.random() * 9000 + 1000)
};
// Save user to local storage
localStorage.setItem('radical_user', JSON.stringify(currentUser));
}
// Create or get user in the database
try {
await createUser(currentUser);
console.log("User registration complete");
} catch (error) {
console.error("Failed to register user on page load");
}
}
// Load verified users data
function loadVerifiedUsers() {
verifiedUsers = JSON.parse(localStorage.getItem('radical_verified_users')) || {};
}
// Setup event listeners
function setupEventListeners() {
const proposalInput = document.getElementById('proposal-input');
const charCounter = document.getElementById('char-counter');
const postButton = document.getElementById('post-button');
const sortSelect = document.getElementById('sort-select');
// Rotate placeholder text every 5 seconds
let placeholderIndex = 0;
setInterval(() => {
if (proposalInput) {
placeholderIndex = (placeholderIndex + 1) % placeholders.length;
proposalInput.placeholder = placeholders[placeholderIndex];
}
}, 5000);
// Update character counter
if (proposalInput) {
proposalInput.addEventListener('input', () => {
const remaining = CHAR_LIMIT - proposalInput.value.length;
charCounter.textContent = `${remaining} characters remaining`;
if (remaining < 20) {
charCounter.classList.add('limit');
} else {
charCounter.classList.remove('limit');
}
// Enable/disable post button
postButton.disabled = proposalInput.value.trim().length === 0;
});
// Sort proposals when sort option changes
if (sortSelect) {
sortSelect.addEventListener('change', fetchProposals);
}
}
}
// Create or get user in DB
async function createUser(user) {
try {
console.log("Creating user:", user); // Log the user data
const response = await fetch(`${API_BASE_URL}/users`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: user.id,
name: user.name
})
});
if (!response.ok) {
const errorData = await response.json();
console.error("User creation API error:", errorData);
throw new Error('Failed to create user');
}
const data = await response.json();
console.log("User creation successful:", data);
return data;
} catch (error) {
console.error('Error creating user:', error);
throw error;
}
}
// Animation for successful post
function animatePostSuccess() {
const successEl = document.createElement('div');
successEl.textContent = '🔥 Petition created!';
successEl.style.position = 'fixed';
successEl.style.bottom = '20px';
successEl.style.left = '50%';
successEl.style.transform = 'translateX(-50%)';
successEl.style.backgroundColor = 'var(--primary)';
successEl.style.color = 'var(--dark)';
successEl.style.padding = '12px 24px';
successEl.style.borderRadius = '0';
successEl.style.zIndex = '1000';
successEl.style.boxShadow = '0 0 20px rgba(255, 0, 153, 0.5)';
successEl.style.transition = 'all 0.4s cubic-bezier(0.16, 1, 0.3, 1)';
successEl.style.opacity = '0';
successEl.style.fontSize = '14px';
successEl.style.fontWeight = '700';
successEl.style.fontFamily = "'Roboto Mono', monospace";
document.body.appendChild(successEl);
// Fade in
setTimeout(() => {
successEl.style.opacity = '1';
}, 100);
// Fade out
setTimeout(() => {
successEl.style.opacity = '0';
setTimeout(() => {
document.body.removeChild(successEl);
}, 500);
}, 3000);
}
// Show loading indicator
function showLoadingIndicator() {
const proposalsContainer = document.getElementById('proposals-container');
if (proposalsContainer) {
proposalsContainer.innerHTML = `
<div class="loading-indicator">
<div class="loading-spinner"></div>
<p>Loading petitions...</p>
</div>
`;
}
}
// Show error message
function showErrorMessage(message, isRetryable = true) {
const proposalsContainer = document.getElementById('proposals-container');
if (proposalsContainer) {
proposalsContainer.innerHTML = `
<div class="error-state">
<p>${message}</p>
${isRetryable ? '<button class="retry-button" onclick="fetchProposals()">Try Again</button>' : ''}
</div>
`;
}
}
// Show success/error toast message
function showToastMessage(message, backgroundColor) {
const toastEl = document.createElement('div');
toastEl.textContent = message;
toastEl.style.position = 'fixed';
toastEl.style.bottom = '20px';
toastEl.style.left = '50%';
toastEl.style.transform = 'translateX(-50%)';
toastEl.style.backgroundColor = backgroundColor;
toastEl.style.color = 'var(--dark)';
toastEl.style.padding = '12px 24px';
toastEl.style.borderRadius = '0';
toastEl.style.zIndex = '1000';
toastEl.style.boxShadow = `0 0 20px ${backgroundColor}99`;
toastEl.style.transition = 'all 0.4s cubic-bezier(0.16, 1, 0.3, 1)';
toastEl.style.opacity = '0';
toastEl.style.fontSize = '14px';
toastEl.style.fontWeight = '700';
toastEl.style.fontFamily = "'Roboto Mono', monospace";
document.body.appendChild(toastEl);
// Fade in
setTimeout(() => {
toastEl.style.opacity = '1';
}, 100);
// Fade out
setTimeout(() => {
toastEl.style.opacity = '0';
setTimeout(() => {
document.body.removeChild(toastEl);
}, 500);
}, 3000);
}
// Fetch proposals from API
async function fetchProposals() {
try {
// Show loading indicator
showLoadingIndicator();
const sortSelect = document.getElementById('sort-select');
const sortBy = sortSelect ? sortSelect.value : 'newest';
const response = await fetch(`${API_BASE_URL}/proposals?sortBy=${sortBy}&userId=${currentUser.id}`);
if (!response.ok) {
throw new Error('Failed to fetch proposals');
}
const proposals = await response.json();
renderProposals(proposals);
} catch (error) {
console.error('Error fetching proposals:', error);
showErrorMessage('Oops! Failed to load petitions. Parliament might be censoring us already!');
}
}
// Handle voting
async function handleVote(proposalId, voteType) {
currentProposalId = proposalId;
currentVoteType = voteType;
// TEMPORARILY DISABLED AEC VERIFICATION
// Simply submit the vote directly regardless of vote type
submitVote(proposalId, voteType, voteType === 'upvote');
/* Original code commented out
// Check if user needs to provide AEC details for petition signing
if (voteType === 'upvote') {
// Check if user already verified for this vote
if (verifiedUsers[currentUser.id] && verifiedUsers[currentUser.id][proposalId]) {
// User already verified for this proposal, proceed with vote
submitVote(proposalId, voteType, true, verifiedUsers[currentUser.id][proposalId].userData);
return;
}
// Show verification modal for petition signing
showVerificationModal(proposalId);
} else {
// Downvotes don't require verification
submitVote(proposalId, voteType, false);
}
*/
}
// Submit vote with petition data if applicable
async function submitVote(proposalId, voteType, isPetition = false, petitionDetails = null) {
try {
const voteData = {
proposalId: proposalId,
userId: currentUser.id,
voteType: voteType
};
// Add petition data if this is a petition signature
if (isPetition && petitionDetails) {
voteData.isPetition = true;
voteData.petitionDetails = petitionDetails;
}
const response = await fetch(`${API_BASE_URL}/votes`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(voteData),
mode: 'cors',
credentials: 'same-origin'
});
if (!response.ok) {
throw new Error('Failed to submit vote');
}
// Refresh proposals to show updated votes
fetchProposals();
} catch (error) {
console.error('Error voting:', error);
showErrorMessage('Hmm, your vote didn\'t count. That\'s a bit suspicious, isn\'t it?');
}
}
// Format date
function formatDate(timestamp) {
const now = new Date();
const date = new Date(timestamp);
const diffMs = now - date;
const diffSec = Math.floor(diffMs / 1000);
const diffMin = Math.floor(diffSec / 60);
const diffHour = Math.floor(diffMin / 60);
const diffDay = Math.floor(diffHour / 24);
if (diffSec < 60) {
return 'just now';
} else if (diffMin < 60) {
return diffMin + (diffMin === 1 ? ' minute ago' : ' minutes ago');
} else if (diffHour < 24) {
return diffHour + (diffHour === 1 ? ' hour ago' : ' hours ago');
} else if (diffDay < 30) {
return diffDay + (diffDay === 1 ? ' day ago' : ' days ago');
} else {
return date.toLocaleDateString();
}
}
// Render proposals - FIXED VERSION
function renderProposals(proposals) {
const proposalsContainer = document.getElementById('proposals-container');
if (!proposalsContainer) return;
// Clear container
proposalsContainer.innerHTML = '';
// Show empty state if no proposals
if (!proposals || proposals.length === 0) {
proposalsContainer.innerHTML = `
<div class="empty-state">
<p>No petitions yet. Be the first to create one!</p>
</div>
`;
return;
}
// Render each proposal
proposals.forEach(proposal => {
const netVotes = proposal.upvotes - proposal.downvotes;
const voteClass = netVotes > 0 ? 'positive' : netVotes < 0 ? 'negative' : '';
const proposalCard = document.createElement('div');
proposalCard.className = 'proposal-card';
// Badge for trending or power proposals
const badgeHtml = proposal.trending ?
`<div class="power-badge trending-badge">🔥 Trending</div>` :
(netVotes > 5 ? `<div class="power-badge">💯 Popular</div>` : '');
// Add petition signatures info if proposal has upvotes/signatures
const petitionInfo = proposal.upvotes > 0 ?
`<div class="petition-info">
<span class="petition-icon">✓</span>
<span class="petition-count">${proposal.petition_signatures || proposal.upvotes}</span>
<span>verified signatures</span>
</div>` : '';
// Add meme if available
const memeHtml = proposal.meme_url ?
`<div class="proposal-meme">
<img src="${proposal.meme_url}" alt="Meme for petition" onerror="this.style.display='none'" />
</div>` : '';
proposalCard.innerHTML = `
${badgeHtml}
<div class="proposal-author">${proposal.author_name}</div>
<div class="proposal-text">${proposal.text}</div>
${memeHtml}
${petitionInfo}
<div class="proposal-stats">
<div class="vote-buttons">
<button class="vote-button ${proposal.userVote === 'upvote' ? 'upvoted' : ''}" data-proposal-id="${proposal.id}" data-vote-type="upvote">
<span class="vote-icon">▲</span>
<span>${proposal.upvotes || 0}</span>
</button>
<button class="vote-button ${proposal.userVote === 'downvote' ? 'downvoted' : ''}" data-proposal-id="${proposal.id}" data-vote-type="downvote">
<span class="vote-icon">▼</span>
<span>${proposal.downvotes || 0}</span>
</button>
<span class="net-votes ${voteClass}">${netVotes > 0 ? '+' : ''}${netVotes}</span>
<button class="share-button" data-proposal-id="${proposal.id}" style="background-color: #000000; border: 0px solid #333333; color: #aaaaaa; padding: 4px 8px; font-size: 20px;">
<img src="https://radical.omar-c29.workers.dev/memes/share-arrow-icon-md.png" alt="Share" class="share-icon" style="width: 20px; height: 14px; filter: brightness(0) invert(1);">
</button>
</div>
<div class="proposal-time">${formatDate(proposal.timestamp)}</div>
</div>
`;
proposalsContainer.appendChild(proposalCard);
// Add event listeners for vote buttons
const upvoteButton = proposalCard.querySelector('[data-vote-type="upvote"]');
const downvoteButton = proposalCard.querySelector('[data-vote-type="downvote"]');
const shareButton = proposalCard.querySelector('.share-button');
if (upvoteButton) {
upvoteButton.addEventListener('click', () => {
handleVote(proposal.id, 'upvote');
});
}
if (downvoteButton) {
downvoteButton.addEventListener('click', () => {
handleVote(proposal.id, 'downvote');
});
}
if (shareButton) {
shareButton.addEventListener('click', () => {
// Copy the shareable link to clipboard
copyToClipboard(getShareableLink(proposal.id));
});
}
});
}
// Create meme upload elements
function createMemeUpload() {
const uploadContainer = document.createElement('div');
uploadContainer.className = 'meme-upload';
const uploadLabel = document.createElement('label');
uploadLabel.className = 'meme-upload-label';
uploadLabel.setAttribute('for', 'meme-upload-input');
const uploadIcon = document.createElement('span');
uploadIcon.className = 'meme-upload-icon';
uploadIcon.textContent = '🖼️';
const uploadText = document.createElement('span');
uploadText.className = 'meme-upload-text';
uploadText.textContent = 'Add a meme to your petition (optional)';
const uploadInput = document.createElement('input');
uploadInput.className = 'meme-upload-input';
uploadInput.id = 'meme-upload-input';
uploadInput.type = 'file';
uploadInput.accept = 'image/jpeg, image/png, image/gif, image/webp';
uploadLabel.appendChild(uploadIcon);
uploadLabel.appendChild(uploadText);
uploadLabel.appendChild(uploadInput);
uploadContainer.appendChild(uploadLabel);
const previewContainer = document.createElement('div');
previewContainer.className = 'meme-preview';
previewContainer.style.display = 'none';
const previewImage = document.createElement('img');
previewImage.id = 'meme-preview-image';
const removeButton = document.createElement('button');
removeButton.className = 'meme-remove';
removeButton.innerHTML = '×';
removeButton.setAttribute('aria-label', 'Remove meme');
const progressContainer = document.createElement('div');
progressContainer.className = 'meme-upload-progress';
progressContainer.style.display = 'none';
const progressBar = document.createElement('div');
progressBar.className = 'meme-upload-progress-bar';
progressContainer.appendChild(progressBar);
previewContainer.appendChild(previewImage);
previewContainer.appendChild(removeButton);
uploadContainer.appendChild(previewContainer);
uploadContainer.appendChild(progressContainer);
return {
container: uploadContainer,
input: uploadInput,
preview: previewContainer,
image: previewImage,
removeBtn: removeButton,
progress: progressContainer,
progressBar: progressBar
};
}
// Initialize meme upload
function initMemeUpload() {
const newProposalSection = document.querySelector('.new-proposal');
if (!newProposalSection) return;
const charCounter = document.getElementById('char-counter');
if (!charCounter) return;
// Create meme upload elements
const memeUpload = createMemeUpload();
// Insert after character counter, before post button
charCounter.after(memeUpload.container);
// Variable to store the selected file
let selectedMeme = null;
// Handle file selection
memeUpload.input.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
// Validate file type
const validTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!validTypes.includes(file.type)) {
showToastMessage('⚠️ Invalid file type. Only JPEG, PNG, GIF, and WebP images are allowed.', '#ff0099');
return;
}
// Validate file size (limit to 2MB)
const MAX_SIZE = 2 * 1024 * 1024; // 2MB
if (file.size > MAX_SIZE) {
showToastMessage('⚠️ File too large. Maximum size is 2MB.', '#ff0099');
return;
}
// Store the file
selectedMeme = file;
// Show preview
const reader = new FileReader();
reader.onload = (event) => {
memeUpload.image.src = event.target.result;
memeUpload.preview.style.display = 'flex';
};
reader.readAsDataURL(file);
});
// Handle remove button
memeUpload.removeBtn.addEventListener('click', () => {
selectedMeme = null;
memeUpload.input.value = '';
memeUpload.preview.style.display = 'none';
memeUpload.image.src = '';
});
// Modify post button click handler to include meme upload
const postButton = document.getElementById('post-button');
if (postButton) {
// Store the original click event handler
const originalClickHandler = postButton.onclick;
// Remove the original handler
postButton.onclick = null;
// Add new handler
postButton.addEventListener('click', async () => {
const proposalInput = document.getElementById('proposal-input');
const text = proposalInput?.value.trim();
if (text) {
try {
const trending = Math.random() > 0.8; // Randomly mark some as trending
// Create the proposal first
const createResponse = await fetch(`${API_BASE_URL}/proposals`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
authorId: currentUser.id,
text: text,
trending: trending
})
});
if (!createResponse.ok) {
throw new Error('Failed to create proposal');
}
const proposalData = await createResponse.json();
const proposalId = proposalData.id;
// If there's a meme, upload it
if (selectedMeme) {
// Show progress bar
memeUpload.progress.style.display = 'block';
memeUpload.progressBar.style.width = '0%';
// Create form data for file upload
const formData = new FormData();
formData.append('proposalId', proposalId);
formData.append('meme', selectedMeme);
// Track upload progress
const xhr = new XMLHttpRequest();
xhr.open('POST', `${API_BASE_URL}/memes`);
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
const percent = (event.loaded / event.total) * 100;
memeUpload.progressBar.style.width = percent + '%';
}
});
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
// Upload successful
memeUpload.progress.style.display = 'none';
// Clear the meme upload
selectedMeme = null;
memeUpload.input.value = '';
memeUpload.preview.style.display = 'none';
memeUpload.image.src = '';
// Show success message
animatePostSuccess();
// Clear input
proposalInput.value = '';
charCounter.textContent = `${CHAR_LIMIT} characters remaining`;
postButton.disabled = true;
// Refresh proposals
fetchProposals();
} else {
// Upload failed
memeUpload.progress.style.display = 'none';
console.error('Meme upload failed:', xhr.responseText);
showToastMessage('Failed to upload meme. Please try again.', '#ff0099');
}
});
xhr.addEventListener('error', () => {
memeUpload.progress.style.display = 'none';
console.error('Meme upload network error');
showToastMessage('Failed to upload meme. Please check your connection.', '#ff0099');
});
// Send the upload
xhr.send(formData);
} else {
// No meme to upload, just show success and refresh
// Clear input
proposalInput.value = '';
charCounter.textContent = `${CHAR_LIMIT} characters remaining`;
postButton.disabled = true;
// Show success animation
animatePostSuccess();
// Refresh proposals
fetchProposals();
}
} catch (error) {
console.error('Error creating proposal:', error);
showErrorMessage('Failed to post your petition. Parliament might be onto us!');
}
}
});
}
}
// Display the modal for petition verification
function showVerificationModal(proposalId) {
// Create the modal element
const modalOverlay = document.createElement('div');
modalOverlay.className = 'modal-overlay';
const modalContainer = document.createElement('div');
modalContainer.className = 'modal-container';
// Modal header
const modalHeader = document.createElement('div');
modalHeader.className = 'modal-header';
const modalTitle = document.createElement('h3');
modalTitle.textContent = 'Sign Official Petition';
const closeButton = document.createElement('button');
closeButton.className = 'modal-close';
closeButton.textContent = '✕';
closeButton.setAttribute('aria-label', 'Close modal');
modalHeader.appendChild(modalTitle);
modalHeader.appendChild(closeButton);
// Modal body
const modalBody = document.createElement('div');
modalBody.className = 'modal-body';
// Find the proposal text to display in the modal
const proposalCard = document.querySelector(`[data-proposal-id="${proposalId}"]`).closest('.proposal-card');
const proposalText = proposalCard.querySelector('.proposal-text').textContent;
// Current proposal context
const petitionContext = document.createElement('p');
petitionContext.innerHTML = `<strong>You are officially petitioning for:</strong>
<div style="margin: 10px 0; padding: 10px; background: rgba(255,255,255,0.05); font-style: italic;">"${proposalText}"</div>
<strong>Your details will be verified with the Australian Electoral Commission (AEC).</strong>`;
const formElement = document.createElement('form');
formElement.className = 'petition-form';
// Form fields
const fields = [
{ id: 'full-name', label: 'Full Name (as registered with AEC)', type: 'text', required: true },
{ id: 'address', label: 'Residential Address', type: 'text', required: true },
{ id: 'postcode', label: 'Postcode', type: 'text', required: true, pattern: '\\d{4}' },
{ id: 'dob', label: 'Date of Birth', type: 'date', required: true },
{ id: 'email', label: 'Email Address', type: 'email', required: true }
];
fields.forEach(field => {
const formGroup = document.createElement('div');
formGroup.className = 'form-group';
const label = document.createElement('label');
label.setAttribute('for', field.id);
label.textContent = field.label;
const input = document.createElement('input');
input.className = 'form-input';
input.id = field.id;
input.type = field.type;
input.required = field.required;
if (field.pattern) {
input.pattern = field.pattern;
}
const errorSpan = document.createElement('span');
errorSpan.className = 'form-error';
errorSpan.id = `${field.id}-error`;
formGroup.appendChild(label);
formGroup.appendChild(input);
formGroup.appendChild(errorSpan);
formElement.appendChild(formGroup);
});
// Consent checkbox
const consentDiv = document.createElement('div');
consentDiv.className = 'consent-checkbox';
const consentCheckbox = document.createElement('input');
consentCheckbox.type = 'checkbox';
consentCheckbox.id = 'petition-consent';
consentCheckbox.required = true;
const consentLabel = document.createElement('label');
consentLabel.setAttribute('for', 'petition-consent');
consentLabel.innerHTML = 'I certify that I am an Australian citizen or eligible resident, registered to vote, and consent to my information being used for this official petition. I also certify that I am excited to join the radical party as a member and take back our sexy country.';
consentDiv.appendChild(consentCheckbox);
consentDiv.appendChild(consentLabel);
formElement.appendChild(consentDiv);
// Legal info
const infoText = document.createElement('p');
infoText.className = 'info-text';
infoText.textContent = 'Your details will be verified with the AEC database. A valid petition requires accurate information matching your electoral enrollment. Multiple signatures from the same person are prohibited.';
formElement.appendChild(infoText);
// Submit button
const submitButton = document.createElement('button');
submitButton.type = 'submit';
submitButton.className = 'petition-submit-btn';
submitButton.textContent = 'Sign Petition';
submitButton.disabled = true;
formElement.appendChild(submitButton);
modalBody.appendChild(petitionContext);
modalBody.appendChild(formElement);
modalContainer.appendChild(modalHeader);
modalContainer.appendChild(modalBody);
modalOverlay.appendChild(modalContainer);
document.body.appendChild(modalOverlay);
// Form validation
const inputs = formElement.querySelectorAll('input:not([type="checkbox"])');
const checkValidation = () => {
let isValid = true;
inputs.forEach(input => {
if (!input.validity.valid) {
isValid = false;
}
});
if (!consentCheckbox.checked) {
isValid = false;
}
submitButton.disabled = !isValid;
};
inputs.forEach(input => {
input.addEventListener('input', checkValidation);
// Show specific error messages
input.addEventListener('invalid', (e) => {
e.preventDefault();
const errorSpan = document.getElementById(`${input.id}-error`);
if (input.validity.valueMissing) {
errorSpan.textContent = 'This field is required';
} else if (input.validity.patternMismatch && input.id === 'postcode') {
errorSpan.textContent = 'Please enter a valid 4-digit postcode';
} else if (input.validity.typeMismatch && input.type === 'email') {
errorSpan.textContent = 'Please enter a valid email address';
} else {
errorSpan.textContent = 'Please enter valid information';
}
});
});
consentCheckbox.addEventListener('change', checkValidation);
// Close button functionality
closeButton.addEventListener('click', () => {
document.body.removeChild(modalOverlay);
});
// Form submission
formElement.addEventListener('submit', async (e) => {
e.preventDefault();
// Show loading state
submitButton.disabled = true;
submitButton.textContent = 'Verifying...';
// Collect form data
const formData = {
fullName: document.getElementById('full-name').value,
address: document.getElementById('address').value,
postcode: document.getElementById('postcode').value,
dob: document.getElementById('dob').value,
email: document.getElementById('email').value,
consent: document.getElementById('petition-consent').checked
};
// Verify with AEC (simulated)
await verifyWithAEC(formData, proposalId);
});
}
// Verify user details with AEC (simulated)
async function verifyWithAEC(userData, proposalId) {
try {
// Show loading state with timeout to simulate verification
await new Promise(resolve => setTimeout(resolve, 1500));
// In a real implementation, this would call your server which would verify with AEC APIs
// For demo purposes, we'll simulate a successful verification
// Store the verified state
if (!verifiedUsers[currentUser.id]) {
verifiedUsers[currentUser.id] = {};
}
// Mark this user as verified for this proposal
verifiedUsers[currentUser.id][proposalId] = {
verified: true,
timestamp: Date.now(),
userData: userData
};
// Save verification data to localStorage (in real app, this would be in your database)
localStorage.setItem('radical_verified_users', JSON.stringify(verifiedUsers));
// Remove the modal
const modal = document.querySelector('.modal-overlay');
document.body.removeChild(modal);
// Show success message
showToastMessage('✓ Petition signed successfully!', '#11cc77');
// Finally submit the vote with petition details
submitVote(proposalId, currentVoteType, true, userData);
} catch (error) {
console.error('Verification error:', error);
showToastMessage('⚠️ Verification failed. Please try again.', '#ff0099');
// Reset the form
const submitButton = document.querySelector('.petition-submit-btn');
if (submitButton) {
submitButton.disabled = false;
submitButton.textContent = 'Sign Petition';
}
}
}
async function checkForProposalInUrl() {
const urlParams = new URLSearchParams(window.location.search);
const proposalId = urlParams.get('proposal');
if (proposalId) {
try {
// Show loading state
showToastMessage('Loading petition...', '#11cc77');
// Fetch the specific proposal
const response = await fetch(`${API_BASE_URL}/proposals?userId=${currentUser.id}`);
if (!response.ok) {
throw new Error('Failed to fetch proposal');
}
const proposals = await response.json();
const targetProposal = proposals.find(p => p.id === proposalId);
if (targetProposal) {
// Create and show the modal with the proposal data
createProposalViewModal(targetProposal);
} else {
showToastMessage('⚠️ Petition not found', '#ff0099');
}
} catch (error) {
console.error('Error fetching proposal:', error);
showToastMessage('⚠️ Failed to load petition', '#ff0099');
}
}
}
// Generate a shareable link for a proposal
function getShareableLink(proposalId) {
const baseUrl = window.location.origin + window.location.pathname;
return `${baseUrl}?proposal=${proposalId}`;
}
// Copy link to clipboard and show feedback
function copyToClipboard(text) {
navigator.clipboard.writeText(text)
.then(() => {
showToastMessage('✓ Link copied to clipboard!', '#11cc77');
// Find all share buttons and reset them
const shareButtons = document.querySelectorAll('.share-button');
shareButtons.forEach(button => {
button.classList.remove('copied');
});
// Find the specific button that was clicked (if any)
const clickedButton = document.querySelector(`.share-button[data-proposal-id="${text.split('proposal=')[1]}"]`);
if (clickedButton) {
clickedButton.classList.add('copied');
setTimeout(() => {
clickedButton.classList.remove('copied');
}, 2000);
}
})
.catch(err => {
console.error('Failed to copy: ', err);
showToastMessage('⚠️ Failed to copy link', '#ff0099');
});
}
// Create a modal to display when a proposal is opened via direct link
function createProposalViewModal(proposal) {
// Create modal elements
const modalOverlay = document.createElement('div');
modalOverlay.className = 'modal-overlay';
const modalContainer = document.createElement('div');
modalContainer.className = 'modal-container proposal-view';
// Modal header
const modalHeader = document.createElement('div');
modalHeader.className = 'modal-header';
const modalTitle = document.createElement('h3');
modalTitle.textContent = 'Petition Details';
const closeButton = document.createElement('button');
closeButton.className = 'modal-close';
closeButton.textContent = '✕';
closeButton.setAttribute('aria-label', 'Close modal');
modalHeader.appendChild(modalTitle);
modalHeader.appendChild(closeButton);
// Modal body
const modalBody = document.createElement('div');
modalBody.className = 'modal-body';
// Add petition info
const netVotes = proposal.upvotes - proposal.downvotes;
const voteClass = netVotes > 0 ? 'positive' : netVotes < 0 ? 'negative' : '';
const badgeHtml = proposal.trending ?
`<div class="power-badge trending-badge" style="position: static; display: inline-block; margin-bottom: 15px;">🔥 Trending</div>` :
(netVotes > 5 ? `<div class="power-badge" style="position: static; display: inline-block; margin-bottom: 15px;">💯 Popular</div>` : '');
modalBody.innerHTML = `
${badgeHtml}
<div class="proposal-author" style="font-size: 1.1rem;">${proposal.author_name}</div>
<div class="proposal-text" style="font-size: 1.3rem; margin: 20px 0;">${proposal.text}</div>
${proposal.meme_url ? `<div class="proposal-meme">
<img src="${proposal.meme_url}" alt="Meme for petition" onerror="this.style.display='none'" />
</div>` : ''}
${proposal.petition_signatures > 0 ? `<div class="petition-info" style="margin: 20px 0;">
<span class="petition-icon">✓</span>
<span class="petition-count">${proposal.petition_signatures || proposal.upvotes}</span>
<span>verified signatures</span>
</div>` : ''}
<div class="proposal-stats" style="margin-top: 20px; padding-top: 20px;">
<div class="vote-buttons">
<button class="vote-button ${proposal.userVote === 'upvote' ? 'upvoted' : ''}" id="modal-upvote" data-proposal-id="${proposal.id}" data-vote-type="upvote">
<span class="vote-icon">▲</span>
<span>${proposal.upvotes || 0}</span>
</button>
<button class="vote-button ${proposal.userVote === 'downvote' ? 'downvoted' : ''}" id="modal-downvote" data-proposal-id="${proposal.id}" data-vote-type="downvote">
<span class="vote-icon">▼</span>
<span>${proposal.downvotes || 0}</span>
</button>
<span class="net-votes ${voteClass}">${netVotes > 0 ? '+' : ''}${netVotes}</span>
</div>
<div class="proposal-time">${formatDate(proposal.timestamp)}</div>
</div>
<div style="margin-top: 25px; border-top: 1px solid var(--border); padding-top: 20px;">
<p style="margin-bottom: 15px; font-size: 0.9rem;">Share this petition:</p>
<div class="share-link-container">
<input type="text" class="share-link-field" value="${getShareableLink(proposal.id)}" readonly>
<button class="share-copy-button" id="modal-copy-link">Copy</button>
</div>
<div class="social-share-buttons">
<button class="social-share-button" id="share-twitter">
<span class="social-icon">🐦</span>
<span>Twitter</span>
</button>
<button class="social-share-button" id="share-facebook">
<span class="social-icon">📘</span>
<span>Facebook</span>
</button>
<button class="social-share-button" id="share-whatsapp">
<span class="social-icon">📱</span>
<span>WhatsApp</span>
</button>
</div>
</div>
`;
modalContainer.appendChild(modalHeader);
modalContainer.appendChild(modalBody);
modalOverlay.appendChild(modalContainer);
document.body.appendChild(modalOverlay);
// Add event listeners
closeButton.addEventListener('click', () => {
document.body.removeChild(modalOverlay);
// Update URL to remove the proposal parameter without page reload
const newUrl = window.location.pathname;
window.history.pushState({}, '', newUrl);
});
const copyLinkButton = document.getElementById('modal-copy-link');
if (copyLinkButton) {
copyLinkButton.addEventListener('click', () => {
const linkField = document.querySelector('.share-link-field');
linkField.select();
copyToClipboard(getShareableLink(proposal.id));
// Add flash animation
copyLinkButton.classList.add('flash-animation');
setTimeout(() => {
copyLinkButton.classList.remove('flash-animation');
}, 500);
});
}
// Add event listeners for vote buttons
const upvoteButton = document.getElementById('modal-upvote');
const downvoteButton = document.getElementById('modal-downvote');
if (upvoteButton) {
upvoteButton.addEventListener('click', () => {
handleVote(proposal.id, 'upvote');
});
}
if (downvoteButton) {
downvoteButton.addEventListener('click', () => {
handleVote(proposal.id, 'downvote');
});
}
// Add event listeners for social share buttons
const twitterButton = document.getElementById('share-twitter');
const facebookButton = document.getElementById('share-facebook');
const whatsappButton = document.getElementById('share-whatsapp');
if (twitterButton) {
twitterButton.addEventListener('click', () => {
const text = `Check out this RADICAL petition: "${proposal.text.substring(0, 100)}${proposal.text.length > 100 ? '...' : ''}"`;
const url = getShareableLink(proposal.id);
window.open(`https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}&url=${encodeURIComponent(url)}`, '_blank');
});
}
if (facebookButton) {
facebookButton.addEventListener('click', () => {
const url = getShareableLink(proposal.id);
window.open(`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(url)}`, '_blank');
});
}
if (whatsappButton) {
whatsappButton.addEventListener('click', () => {
const text = `Check out this RADICAL petition: "${proposal.text.substring(0, 100)}${proposal.text.length > 100 ? '...' : ''}"`;
const url = getShareableLink(proposal.id);
window.open(`https://wa.me/?text=${encodeURIComponent(text + ' ' + url)}`, '_blank');
});
}
}
</script>
</body>
</html>