A single-board anonymous idea submission and voting platform built with vanilla JavaScript and Supabase, styled with the UAB design system.
- Anonymous Posting: Submit ideas without authentication
- Upvoting: Vote on ideas you like
- Duplicate Vote Prevention: Each user can only vote once per idea
- Real-time Updates: See new ideas appear instantly
- Responsive Design: Works on desktop, tablet, and mobile
- UAB Branding: Professional styling with UAB colors and typography
- CAPTCHA Protection: Cloudflare Turnstile prevents bot spam
- Profanity Filter: Automatic filtering using leo-profanity library
- Frontend: Vanilla JavaScript, HTML5, CSS3
- Styling: Tailwind CSS (CDN) with UAB custom colors
- Database: Supabase (PostgreSQL)
- Icons: Font Awesome 6
- Hosting: Any static hosting (Netlify, Vercel, GitHub Pages, etc.)
- Go to Supabase and sign up/login
- Click "New Project"
- Fill in your project details and wait for it to be created
- Go to Project Settings > API to find your credentials
Run these SQL commands in the Supabase SQL Editor:
-- Create ideas table
CREATE TABLE ideas (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
content TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
vote_count INTEGER DEFAULT 0
);
-- Create votes table for duplicate prevention
CREATE TABLE votes (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
idea_id UUID NOT NULL REFERENCES ideas(id) ON DELETE CASCADE,
voter_fingerprint TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(idea_id, voter_fingerprint)
);
-- Create index for better performance
CREATE INDEX idx_ideas_vote_count ON ideas(vote_count DESC);
CREATE INDEX idx_votes_idea_id ON votes(idea_id);Create a trigger to automatically increment vote_count when a vote is added:
-- Function to increment vote count
CREATE OR REPLACE FUNCTION increment_vote_count()
RETURNS TRIGGER AS $$
BEGIN
UPDATE ideas
SET vote_count = vote_count + 1
WHERE id = NEW.idea_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Trigger on vote insert
CREATE TRIGGER on_vote_insert
AFTER INSERT ON votes
FOR EACH ROW
EXECUTE FUNCTION increment_vote_count();Enable RLS and create policies for anonymous access:
-- Enable RLS on both tables
ALTER TABLE ideas ENABLE ROW LEVEL SECURITY;
ALTER TABLE votes ENABLE ROW LEVEL SECURITY;
-- Allow anyone to read ideas
CREATE POLICY "Anyone can view ideas"
ON ideas FOR SELECT
TO public
USING (true);
-- Allow anyone to insert ideas
CREATE POLICY "Anyone can insert ideas"
ON ideas FOR INSERT
TO public
WITH CHECK (true);
-- Allow anyone to insert votes
CREATE POLICY "Anyone can insert votes"
ON votes FOR INSERT
TO public
WITH CHECK (true);
-- Allow anyone to read votes (for duplicate checking)
CREATE POLICY "Anyone can view votes"
ON votes FOR SELECT
TO public
USING (true);In the Supabase dashboard, enable realtime for the ideas table:
Option 1: Using the Table Editor
- Go to Database > Tables
- Find the
ideastable and click on it - Click the Enable Realtime toggle (or it may already be enabled by default)
Option 2: Using SQL
ALTER PUBLICATION supabase_realtime ADD TABLE ideas;Option 3: Using the API Settings
- Go to Project Settings > API
- Under Realtime, ensure realtime is enabled for your project
- Realtime should be enabled by default for all tables in newer Supabase projects
Note: In most recent Supabase projects, realtime is automatically enabled for all tables. You can verify by checking if broadcasts work when testing the app.
- Go to Cloudflare Turnstile
- Sign up/login to Cloudflare
- Create a new Turnstile site:
- Site name: "Idea Board" (or whatever you prefer)
- Domain: Your deployment domain (e.g.,
your-app.netlify.app) orlocalhostfor testing - Widget mode: Managed
- Copy your Site Key
- Open
index.htmland replaceYOUR_TURNSTILE_SITE_KEY_HEREwith your actual site key (around line 93)
Note: For local development, add localhost as an allowed domain in Turnstile settings.
- Clone or download this repository
- Open
js/config.js - Replace the placeholder values with your Supabase credentials:
const SUPABASE_URL = 'https://your-project.supabase.co';
const SUPABASE_PUBLISHABLE_KEY = 'sb_publishable_...';You can find these values in your Supabase project under Settings > API:
- Project URL: Copy the full URL (e.g.,
https://abcdefgh.supabase.co) - Publishable key: Copy the "default" publishable key (starts with
sb_publishable_)- Note: If you see "anon key" instead (legacy format), use that - it works the same way
Since this is a static site, you need a local server to run it:
Option 1: Python
# Python 3
python -m http.server 8000
# Python 2
python -m SimpleHTTPServer 8000Option 2: Node.js (http-server)
npx http-server -p 8000Option 3: VS Code Live Server
- Install the "Live Server" extension
- Right-click on
index.htmland select "Open with Live Server"
Then open your browser to http://localhost:8000
redclone/
├── index.html # Main HTML file
├── css/
│ └── style.css # Custom CSS styles
├── js/
│ ├── config.js # Supabase configuration
│ ├── ui.js # UI rendering functions
│ └── app.js # Main application logic
├── README.md # This file
├── SKILL.md # UAB Joomla design skill reference
└── annual-report-patterns.md # UAB design patterns
- Each user gets a unique voter ID stored in localStorage
- When voting, the app inserts a record into the
votestable with the voter ID - A database trigger automatically increments the
vote_counton theideastable - The UNIQUE constraint on (idea_id, voter_fingerprint) prevents duplicate votes
- The UI tracks voted ideas in localStorage for instant feedback
- Uses Supabase real-time subscriptions
- Listens for INSERT events on the
ideastable - Automatically reloads the list when new ideas are posted
- No authentication required
- No personal data collected
- Simple and immediate idea submission
- Push your code to GitHub
- Connect your repository to Netlify
- Deploy (no build command needed)
- Push your code to GitHub
- Import project in Vercel
- Deploy
- Push code to GitHub
- Go to Settings > Pages
- Select your branch and root directory
- Your site will be live at
https://username.github.io/redclone
Upload the files to Supabase Storage and enable public access.
You can embed this idea board into a Joomla website using several methods:
- Deploy the app to Netlify/Vercel first (get a live URL)
- Create a new Joomla article
- Switch to HTML/Code editor (toggle off the WYSIWYG editor)
- Paste this code:
<iframe
src="https://your-app.netlify.app"
width="100%"
height="900px"
frameborder="0"
style="border: none; min-height: 900px;"
></iframe>- Set Page Class to
layout-wide(Menu Item → Page Display Options → Page Class)- This removes the sidebar for full-width display
Pros: Simple, keeps app separate, easy updates Cons: iFrame limitations (scrolling, SEO)
Use the pre-made single-file version:
- Open
joomla-embed.htmlin this repository - Copy the ENTIRE file contents
- Create a new Joomla article
- Set Page Class to
layout-wide(Menu Item → Page Display Options → Page Class) - Switch to HTML/Code editor (toggle off WYSIWYG)
- Paste the entire contents
- Update the configuration section at the top:
- Replace
SUPABASE_URLwith your Supabase project URL - Replace
SUPABASE_PUBLISHABLE_KEYwith your key
- Replace
- Save and publish
That's it! The file includes all CSS, JavaScript, and HTML in one easy-to-paste document.
Manual approach (if you need to customize):
<!-- CDN Libraries -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
prefix: 'tw-',
theme: {
extend: {
colors: {
'uab-green': '#1A5632',
'dragons-lair-green': '#033319',
'campus-green': '#90D408',
'loyal-yellow': '#FDB913',
'smoke-gray': '#808285',
'smoke-gray-7': '#F5F5F5',
'smoke-gray-15': '#DADADA',
}
}
}
}
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2.39.7/dist/umd/supabase.min.js"></script>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<!-- Inline CSS -->
<style>
/* Paste contents of css/style.css here */
</style>
<!-- App HTML -->
<div class="tw-bg-gray-100 tw-min-h-screen">
<div class="tw-max-w-3xl tw-mx-auto tw-px-4 tw-py-6">
<!-- Paste all HTML from index.html body here -->
</div>
</div>
<!-- JavaScript -->
<script>
// Paste contents of js/config.js here
// Paste contents of js/ui.js here
// Paste contents of js/app.js here
</script>Pros: Seamless integration, no iframe issues, full control Cons: More complex, harder to update
- Go to Extensions → Modules → New
- Select "Custom HTML"
- Paste the embedded code (same as Option 2)
- Select module position (e.g.,
top,upper,lower,bottom) - Publish the module
- Set "Prepare Content" to Yes (Advanced tab)
Module Positions:
top- Full-width above content and sidebarsupper- Above content onlylower- Below content onlybottom- Full-width below content and sidebars
Pros: Reusable across multiple pages, flexible positioning Cons: Limited width in some positions
Create a custom Joomla component for professional integration. See Joomla documentation for component development.
If you're using the UAB Joomla template:
- Use Option 2 (Direct HTML Embed) for best results
- Page Class: Set to
layout-widefor full-width pages - UAB Styling: The app already uses UAB brand colors and will integrate seamlessly
- Menu Item Settings:
- Title: "Idea Board" (or custom)
- Alias:
idea-board - Page Class:
layout-wide
- Update Supabase credentials in the embedded JavaScript code
- Update Turnstile site key to match your Joomla domain
- Test thoroughly after embedding
- Clear Joomla cache after making changes (System → Clear Cache)
Edit the Tailwind config in index.html (lines 11-30) to change UAB colors.
Update the maxlength attribute in index.html (line 73) and the character counter logic.
Comment out the setupRealtimeSubscription() call in js/app.js.
- Chrome/Edge: Full support
- Firefox: Full support
- Safari: Full support (with Kulturista font fallback)
- Mobile browsers: Full support
- The Supabase publishable key (or anon key in legacy projects) is safe to expose in client-side code when RLS policies are properly configured
- All database access is controlled by Row Level Security policies
- No sensitive data is stored or transmitted
- Consider adding rate limiting for production use
- Never expose your secret key in client-side code - it's only for server-side use
- Check browser console for errors
- Verify Supabase credentials in
js/config.js - Ensure RLS policies are created correctly
- Check that the database trigger is created
- Verify RLS policies allow INSERT on votes table
- Check browser console for errors
- Ensure realtime is enabled for the
ideastable in Supabase - Check that your browser supports WebSockets
- Verify no browser extensions are blocking WebSocket connections
MIT License - Feel free to use this project for any purpose.