2210 lines
No EOL
78 KiB
HTML
2210 lines
No EOL
78 KiB
HTML
<!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>
|
||
<!-- <meta property="og:image" content="${getShareableImageUrl(proposal)}">
|
||
<meta property="twitter:image" content="${getShareableImageUrl(proposal)}"> -->
|
||
<link rel="icon" href="https://radical.omar-c29.workers.dev/memes/favicon.jpg" type="image/png">
|
||
<!-- Primary Meta Tags -->
|
||
<meta property="og:image" content="https://radical.omar-c29.workers.dev/memes/mickeymeta.PNG">
|
||
<meta name="title" content="RADICAL - The Underground Parliament">
|
||
<meta name="description" content="Post dank policy memes, build movements, hack the system. Seize democracy from the elites.">
|
||
<meta name="theme-color" content="#ff0099">
|
||
|
||
<!-- Open Graph / Facebook -->
|
||
<meta property="og:type" content="website">
|
||
<meta property="og:url" content="https://theradicalparty.com/">
|
||
<meta property="og:title" content="RADICAL - The Underground Parliament">
|
||
<meta property="og:description" content="Post dank policy memes, build movements, hack the system. Take back control.">
|
||
<meta property="og:image" content="https://radical.omar-c29.workers.dev/memes/mickeymeta.PNG">
|
||
|
||
<!-- Twitter -->
|
||
<meta property="twitter:card" content="summary_large_image">
|
||
<meta property="twitter:url" content="https://theradicalparty.com/">
|
||
<meta property="twitter:title" content="RADICAL - Democratic Revolution">
|
||
<meta property="twitter:description" content="The underground parliament where we hack the system.">
|
||
<meta property="twitter:image" content="https://radical.omar-c29.workers.dev/memes/mickeymeta.PNG">
|
||
|
||
<!-- App-like behavior for mobile -->
|
||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||
<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 🇦🇺 © 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 = [
|
||
"Drop your revolutionary idea here... (280 character limit)",
|
||
"How would you hack Australia's system? (280 character limit)",
|
||
"What dank policy should replace the current shit? (280 character limit)",
|
||
"Your move to seize democracy from the elites? (280 character limit)",
|
||
"Post the change that would make Australia actually based (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 = 'Radicalise, Vote, Veto';
|
||
|
||
const headerDesc = document.querySelector('header p');
|
||
if (headerDesc) {
|
||
headerDesc.textContent = 'The underground parliament. Post dank memes, build movements, hack the system. 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 = 'Drop your revolutionary idea here... (280 character limit)';
|
||
}
|
||
|
||
const postButton = document.getElementById('post-button');
|
||
if (postButton) {
|
||
postButton.textContent = 'Launch Rebellion';
|
||
}
|
||
}
|
||
|
||
// 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('img');
|
||
uploadIcon.className = 'meme-upload-icon';
|
||
uploadIcon.src = 'https://radical.omar-c29.workers.dev/memes/pepe.jpg';
|
||
uploadIcon.alt = 'Pepe';
|
||
uploadIcon.style.width = '20px';
|
||
uploadIcon.style.height = '20px';
|
||
uploadIcon.style.marginRight = '8px';
|
||
|
||
const uploadText = document.createElement('span');
|
||
uploadText.className = 'meme-upload-text';
|
||
uploadText.textContent = 'Add a meme to your rebellion (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');
|
||
});
|
||
}
|
||
}
|
||
|
||
function getShareableImageUrl(proposal) {
|
||
// Base Cloudinary URL (you would need to sign up for Cloudinary)
|
||
const cloudinaryUrl = "https://res.cloudinary.com/your-cloud-name/image/fetch";
|
||
|
||
// Encode the proposal text for URL
|
||
const encodedText = encodeURIComponent(proposal.text);
|
||
const encodedAuthor = encodeURIComponent(proposal.author_name);
|
||
|
||
// Transformation parameters for text overlay
|
||
const textParams =
|
||
"l_text:Roboto_32_bold:" + encodedText +
|
||
",co_white,w_500,c_fit/" +
|
||
"l_text:Roboto_20:Posted%20by%20" + encodedAuthor +
|
||
",co_rgb:aaaaaa,g_north_west,y_80,x_50";
|
||
|
||
// URL of your template image in R2
|
||
const templateUrl = encodeURIComponent("https://radical.omar-c29.workers.dev/memes/petition-template.png");
|
||
|
||
// Combine everything
|
||
return `${cloudinaryUrl}/${textParams}/${templateUrl}`;
|
||
}
|
||
</script>
|
||
</body>
|
||
</html> |