“The Button” is a multiplayer social experiment game inspired by Reddit’s famous r/thebutton April Fools’ experiment. Players collaborate to prevent a countdown timer from reaching zero by pressing a central button, with each press resetting the timer and earning the player a spot on the leaderboard with a color corresponding to when they pressed the button.
button/
├── index.html # Main game application
├── cnfg.js # Firebase configuration file
└── favicon.ico # Site icon (referenced but not documented)
// 12-hour countdown timer
const maxSeconds = 12 * 60 * 60; // Total seconds in 12 hours
let timerFinishTime; // Absolute finish time
let colour = ''; // Current button color based on remaining time
// Timer calculation based on last button press
timerFinishTime = timestamp + (12 * 60 * 60 * 1000); // 12 hours from last press
// Progressive color zones based on remaining time percentage
const gradientSections = [
{ colour: '#6F41B1', range: 0.7 }, // Purple - 70-100% remaining
{ colour: '#4D86F7', range: 0.45 }, // Blue - 45-70% remaining
{ colour: '#4CA84C', range: 0.25 }, // Green - 25-45% remaining
{ colour: '#F6BD44', range: 0.13 }, // Yellow - 13-25% remaining
{ colour: '#DC6C33', range: 0.05 }, // Orange - 5-13% remaining
{ colour: '#E14331', range: 0.0 }, // Red - 0-5% remaining
];
// Dynamic color assignment based on timer percentage
for (const section of gradientSections) {
if (percentage >= section.range) {
colour = section.colour;
break;
}
}
// Firebase Realtime Database setup
const firebaseConfig = {
apiKey: "AIzaSyChiGyL3rORNm5AVZ632wN92AqfhDKfNDs",
authDomain: "joshbruceonline-button.firebaseapp.com",
databaseURL: "https://joshbruceonline-button-default-rtdb.europe-west1.firebasedatabase.app",
projectId: "joshbruceonline-button",
storageBucket: "joshbruceonline-button.appspot.com",
messagingSenderId: "1066626412012",
appId: "1:1066626412012:web:cba79f7dbf59a194a4e4c9"
};
// Firebase Realtime Database structure
{
"leaderboard": {
"[entryId]": {
"name": "PlayerName", // Player display name (max 5 chars)
"score": 25200, // Seconds remaining when pressed
"colour": "#4D86F7", // Color zone when pressed
"timestamp": 1643723400000 // Server timestamp of button press
}
}
}
// Listen for real-time leaderboard updates
const leaderboardRef = firebase.database().ref("leaderboard");
leaderboardRef.on("value", function (snapshot) {
const leaderboardEntries = [];
snapshot.forEach(function (childSnapshot) {
const entry = childSnapshot.val();
leaderboardEntries.push(entry);
});
// Sort by score in descending order (longest waits first)
leaderboardEntries.sort(function (a, b) {
return b.score - a.score; // Note: Original has bug (+ instead of -)
});
updateLeaderboardDisplay(leaderboardEntries);
});
/* Prominent circular button with dynamic borders */
button {
background-color: #B1B1B1;
color: white;
padding: 100px 100px; /* Large clickable area */
font-size: 18px;
cursor: pointer;
border-radius: 50%; /* Perfect circle */
display: block;
margin: 0 auto;
}
/* Active state feedback */
button:active {
background-color: #9B9D9E; /* Darker on press */
}
/* Dynamic color border system */
const borderStyle = `
0 0 0 10px #909090, /* Inner gray border */
0 0 0 17px #DEDEDE, /* Middle light border */
0 0 0 27px ${colour} /* Outer color-coded border */
`;
button.style.boxShadow = borderStyle;
/* Color-coded progress bar reflecting time zones */
.status-bar {
height: 40px;
background-color: #ccc;
border-radius: 20px;
overflow: hidden;
position: relative;
/* Rainbow gradient background matching color zones */
background-image: linear-gradient(to right,
#6F41B1 0%, #6F41B1 30%, /* Purple zone */
#4D86F7 30%, #4D86F7 55%, /* Blue zone */
#4CA84C 55%, #4CA84C 75%, /* Green zone */
#F6BD44 75%, #F6BD44 87%, /* Yellow zone */
#DC6C33 87%, #DC6C33 95%, /* Orange zone */
#E14331 95%, #E14331 100% /* Red zone */
);
}
/* Moving indicator line */
#status-line {
height: 100%;
background-color: black;
width: 3px;
position: absolute;
top: 0;
right: 0;
transition: right 1s linear; /* Smooth movement animation */
}
// Comprehensive banned words list
const bannedNamesString = "cock,balls,ballz,pussy,fuck,shit,cunt,suck,bitch,nig,penis,france,french,fag,kill,uh,ass,arse,nonce,piss,pedo,slut,whore,twat,ret,ard,hate,gay,ligma,slay,tran,gun";
const bannedNames = bannedNamesString.split(",");
// Case-insensitive substring matching
function isNameBanned(name, bannedNames) {
const lowercaseName = name.toLowerCase();
for (const bannedName of bannedNames) {
if (lowercaseName.includes(bannedName)) {
return true;
}
}
return false;
}
// Client-side validation
if (name.length > 5) {
alert("Names can't be more than 5 characters in length!");
return;
}
if (isNameBanned(name, bannedNames)) {
alert("Names will be displayed on the leaderboard. This name includes restricted words! Please enter another name.");
return;
}
// Automatic deletion of invalid entries
function deleteLongNames() {
const leaderboardRef = firebase.database().ref("leaderboard");
leaderboardRef.once("value", function (snapshot) {
snapshot.forEach(function (childSnapshot) {
const entry = childSnapshot.val();
const entryKey = childSnapshot.key;
if (entry.name.length > 5) {
leaderboardRef.child(entryKey).remove();
}
});
});
}
// Real-time validation for new entries
firebase.database().ref("leaderboard").on("child_added", function (snapshot) {
const entry = snapshot.val();
const entryKey = snapshot.key;
if (entry.name.length > 5) {
firebase.database().ref("leaderboard").child(entryKey).remove();
}
});
function incrementTimer() {
const currentTime = new Date().getTime();
const timeDiff = timerFinishTime - currentTime;
if (timeDiff > 0) {
const secondsLeft = Math.floor(timeDiff / 1000);
const hours = Math.floor(secondsLeft / 3600);
const minutes = Math.floor((secondsLeft % 3600) / 60);
const seconds = secondsLeft % 60;
// Display formatted time
const shownTime = `${hours} Hours ${minutes} Minutes ${seconds} Seconds left`;
document.getElementById("shown-time").innerText = shownTime;
updateStatusBar(); // Update visual progress
} else {
clearInterval(timerInterval);
// Timer expired - button "dies"
}
}
// Update every second
const timerInterval = setInterval(incrementTimer, 1000);
function refreshCountdown() {
database
.ref("leaderboard")
.orderByChild("timestamp")
.limitToLast(1) // Get most recent button press
.once("value")
.then(function (snapshot) {
snapshot.forEach(function (childSnapshot) {
const entry = childSnapshot.val();
const timestamp = entry.timestamp;
if (timestamp) {
// Calculate new finish time (12 hours from last press)
timerFinishTime = timestamp + (12 * 60 * 60 * 1000);
const secondsLeft = Math.floor((timerFinishTime - Date.now()) / 1000);
timerValue.innerText = formatTime(secondsLeft);
}
});
});
}
button.addEventListener("click", function () {
const name = prompt("Enter your name:");
const score = parseInt(timerValue.innerText); // Current remaining seconds
if (name && isValidName(name)) {
const database = firebase.database();
const serverTimestamp = firebase.database.ServerValue.TIMESTAMP;
// Submit button press to Firebase
database.ref("leaderboard").push({
name: name,
score: score, // Seconds remaining when pressed
colour: colour, // Current color zone
timestamp: serverTimestamp // Server-side timestamp
});
button.disabled = false;
}
});
function formatTime(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = seconds % 60;
return `${hours} hours, ${minutes} minutes, ${remainingSeconds} seconds`;
}
function updateStatusBar() {
const secondsLeft = parseInt(timerValue.innerText);
const maxSeconds = 12 * 60 * 60; // 12 hours total
const percentage = (secondsLeft / maxSeconds);
// Calculate position on progress bar
const statusBarWidth = document.querySelector(".status-bar").offsetWidth;
const statusLineWidth = document.querySelector("#status-line").offsetWidth;
const rightPosition = (statusBarWidth - statusLineWidth) * percentage;
// Move status line to correct position
statusBar.style.right = rightPosition + "px";
// Determine color zone and update button border
determineColorZone(percentage);
}
The game implements several psychological mechanisms:
// Color meanings and psychological impact:
// Purple (#6F41B1) - Luxury, early safety (70-100% remaining)
// Blue (#4D86F7) - Trust, moderate safety (45-70% remaining)
// Green (#4CA84C) - Growth, balanced risk (25-45% remaining)
// Yellow (#F6BD44) - Caution, increasing tension (13-25% remaining)
// Orange (#DC6C33) - Warning, high risk (5-13% remaining)
// Red (#E14331) - Danger, maximum urgency (0-5% remaining)
// Disable right-click context menu
document.addEventListener('contextmenu', function(e) {
e.preventDefault();
});
// Modern browser features required:
// - Firebase SDK v8.6.1 support
// - CSS box-shadow and transitions
// - JavaScript ES6 features (const, arrow functions)
// - Realtime Database WebSocket connections
// Comprehensive usage analytics
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-WPNF0DNSMX');
// BUG: Incorrect sorting operation
leaderboardEntries.sort(function (a, b) {
return b.score + a.score; // Should be: b.score - a.score
});
// This causes addition instead of comparison, breaking sort order
timerValue.innerText assignmentsbutton.disabled = false called unnecessarilyBased on Reddit’s April Fools’ 2015 experiment “The Button,” which became a massive social phenomenon with:
“The Button” represents a sophisticated implementation of a social experiment game that successfully captures the psychological tension and community dynamics of the original Reddit phenomenon. The technical implementation demonstrates strong understanding of real-time web applications, Firebase integration, and user experience design.
Technical Rating: 8.4/10
The application successfully creates an engaging social experience that combines technical excellence with psychological game design, making it both a technical achievement and a compelling social experiment.