setup()
setup() runs once at startup. Here it initializes the canvas, grabs DOM elements, loads or creates the shared state, and pre-creates all particle objects. The event listeners ensure the sketch stays in sync with other tabs and responds to user input (music, admin keys).
🔬 These two loops populate the animation arrays. What happens if you delete the emoji loop or change EMOJI_COUNT to 0? Will the party still work without emojis?
// Create confetti & emoji entities
for (let i = 0; i < CONFETTI_COUNT; i++) {
confettiPieces.push(new ConfettiPiece());
}
for (let i = 0; i < EMOJI_COUNT; i++) {
emojiSprites.push(new FloatingEmoji());
}
function setup() {
createCanvas(windowWidth, windowHeight);
colorMode(RGB);
textFont(
'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
);
textAlign(CENTER, CENTER);
noStroke();
ensureTabId();
// Grab DOM references
playerCountEl = document.getElementById("player-count");
thresholdEl = document.getElementById("threshold-value");
modeLabelEl = document.getElementById("mode-label");
countdownContainerEl = document.getElementById("countdown-container");
countdownSecondsEl = document.getElementById("countdown-seconds");
musicToggleEl = document.getElementById("music-toggle");
partyBannerEl = document.getElementById("party-banner");
adminDashboardEl = document.getElementById("admin-dashboard");
adminThresholdInputEl = document.getElementById("admin-threshold-input");
adminApplyThresholdEl = document.getElementById("admin-apply-threshold");
adminStartPartyEl = document.getElementById("admin-start-party");
adminEndPartyEl = document.getElementById("admin-end-party");
adminResetStatsEl = document.getElementById("admin-reset-stats");
statTotalPartiesEl = document.getElementById("stat-total-parties");
statTotalJoinsEl = document.getElementById("stat-total-joins");
statLastPartyEl = document.getElementById("stat-last-party");
adminModalEl = document.getElementById("admin-modal");
adminPasswordInputEl = document.getElementById("admin-password-input");
adminLoginEl = document.getElementById("admin-login-button");
adminCancelEl = document.getElementById("admin-cancel-button");
adminErrorEl = document.getElementById("admin-error");
// Initialize state and register this player
const initial = loadState();
pruneStalePlayers(initial);
localStorage.setItem(STATE_KEY, JSON.stringify(initial));
applyStateToLocal(initial);
registerPlayer();
// Heartbeat & disconnect
setInterval(heartbeat, HEARTBEAT_INTERVAL_MS);
window.addEventListener("beforeunload", () => {
try {
leaveLobby();
} catch (e) {
// ignore
}
});
// Listen for updates from other tabs
window.addEventListener("storage", (e) => {
if (e.key === STATE_KEY && e.newValue) {
try {
const state = JSON.parse(e.newValue);
applyStateToLocal(state);
} catch (err) {
console.error("Party Portal: failed to parse storage state", err);
}
}
});
// Secret admin sequence
document.addEventListener("keydown", handleSecretKey);
// Music button
if (musicToggleEl) {
musicToggleEl.addEventListener("click", () => {
if (musicOn) {
stopMusic();
} else {
startMusic();
musicToggleEl.textContent = "Stop Music";
}
});
}
// Admin login modal actions
if (adminCancelEl) {
adminCancelEl.addEventListener("click", () => {
hideAdminLogin();
adminErrorEl.textContent = "";
});
}
if (adminLoginEl) {
adminLoginEl.addEventListener("click", async () => {
adminErrorEl.textContent = "";
const pwd = adminPasswordInputEl.value || "";
if (!pwd) {
adminErrorEl.textContent = "Please enter a password.";
return;
}
adminLoginEl.disabled = true;
const ok = await verifyAdminPassword(pwd);
adminLoginEl.disabled = false;
if (ok) {
hideAdminLogin();
showAdminDashboard();
} else {
if (!adminErrorEl.textContent) {
adminErrorEl.textContent = "Incorrect password.";
}
}
});
}
if (adminPasswordInputEl) {
adminPasswordInputEl.addEventListener("keyup", (e) => {
if (e.key === "Enter") {
adminLoginEl.click();
}
});
}
// Admin dashboard controls
if (adminApplyThresholdEl) {
adminApplyThresholdEl.addEventListener("click", () => {
if (!isAdmin) return;
const v = parseInt(adminThresholdInputEl.value, 10);
if (!Number.isFinite(v) || v < 1 || v > 200) {
alert("Please enter a threshold between 1 and 200.");
return;
}
updateSharedState((state) => {
state.threshold = v;
});
});
}
if (adminStartPartyEl) {
adminStartPartyEl.addEventListener("click", () => {
if (!isAdmin) return;
startPartyManual();
});
}
if (adminEndPartyEl) {
adminEndPartyEl.addEventListener("click", () => {
if (!isAdmin) return;
endParty();
});
}
if (adminResetStatsEl) {
adminResetStatsEl.addEventListener("click", () => {
if (!isAdmin) return;
if (confirm("Reset statistics for everyone?")) {
resetStats();
}
});
}
// Create confetti & emoji entities
for (let i = 0; i < CONFETTI_COUNT; i++) {
confettiPieces.push(new ConfettiPiece());
}
for (let i = 0; i < EMOJI_COUNT; i++) {
emojiSprites.push(new FloatingEmoji());
}
}
Line-by-line explanation (14 lines)
🔧 Subcomponents:
playerCountEl = document.getElementById("player-count");
Caches references to all HTML elements so they can be updated when state changes
const initial = loadState();
Loads the shared state from localStorage or creates a default state if none exists
window.addEventListener("storage", (e) => {
Listens for localStorage changes from other tabs and syncs the local state automatically
for (let i = 0; i < CONFETTI_COUNT; i++) {
Pre-creates all confetti and emoji instances that will animate during Party Mode
createCanvas(windowWidth, windowHeight);- Creates a full-window canvas that fills the entire viewport and will be drawn on every frame
colorMode(RGB);- Sets color mode to RGB so fill() and other color functions use red, green, blue values (0-255)
textAlign(CENTER, CENTER);- Centers all text both horizontally and vertically so emoji position is at their center point
noStroke();- Disables outline drawing so all shapes (confetti, etc.) have solid fills only
ensureTabId();- Generates a unique ID for this browser tab using sessionStorage, used to track players across state updates
const initial = loadState();- Retrieves the shared state from localStorage; if corrupt or missing, returns a default empty state
pruneStalePlayers(initial);- Removes any player entries older than 15 seconds, cleaning up tabs that closed without properly unregistering
registerPlayer();- Adds this tab to the shared players list with a current timestamp, triggering auto-party logic if threshold is met
setInterval(heartbeat, HEARTBEAT_INTERVAL_MS);- Sets up a repeating heartbeat every 5 seconds to keep this tab's player entry alive and trigger party checks
window.addEventListener("beforeunload", () => {- When the user closes this tab, leaveLobby() removes it from the shared player list so the party auto-ends if threshold drops
window.addEventListener("storage", (e) => {- Listens for changes to localStorage from OTHER tabs and syncs the local state so the display updates instantly
document.addEventListener("keydown", handleSecretKey);- Watches for keyboard input to detect the hidden R, T, Y admin sequence
for (let i = 0; i < CONFETTI_COUNT; i++) {- Creates 180 confetti piece objects in the array before draw() runs, so they are ready to animate
for (let i = 0; i < EMOJI_COUNT; i++) {- Creates 40 emoji sprite objects in the array so they exist when Party Mode starts