JSFiddle - React, Tailwind, and code Playground
HTML
<main class="anniversary-window" id="anniversaryWindow">
<div class="shade" aria-hidden="true"></div>
<div
class="floating-ribbons"
id="floatingRibbons"
aria-hidden="true"
></div>
<canvas
class="fireworks"
id="fireworks"
aria-hidden="true"
></canvas>
<section class="content" aria-labelledby="anniversaryTitle">
<p class="eyebrow">CELEBRATE WITH US</p>
<h1 id="anniversaryTitle">
Rolife 9th Anniversary
</h1>
<p class="subtitle">
Nine years of tiny worlds, wonderful stories and shared joy.
</p>
<button class="join-button" id="joinButton" type="button">
<span class="button-sparkle" aria-hidden="true">✦</span>
<span id="buttonText">
Join Rolife 9th Anniversary
</span>
</button>
<p class="counter" aria-live="polite">
<span class="online-dot" aria-hidden="true"></span>
<span id="counterText">
Loading celebration count…
</span>
</p>
<p
class="status-message"
id="statusMessage"
aria-live="polite"
></p>
</section>
<div
class="success-card"
id="successCard"
role="status"
aria-live="polite"
>
<span class="success-icon">♥</span>
<strong>
You’re part of the celebration!
</strong>
<span>
Thank you for celebrating nine wonderful years with Rolife.
</span>
</div>
</main>
CSS
:root {
--cream: #fff8e9;
--gold: #ffd676;
--coral: #ff6f78;
--rose: #db4267;
--ink: #593244;
--glass: rgba(82, 33, 48, 0.28);
}
* {
box-sizing: border-box;
}
body {
min-height: 100vh;
margin: 0;
display: grid;
place-items: center;
overflow-x: hidden;
background: #271b25;
color: var(--cream);
font-family: "Trebuchet MS", "Avenir Next", Arial, sans-serif;
}
button,
input {
font: inherit;
}
.anniversary-window {
position: relative;
isolation: isolate;
width: min(100vw, 1440px);
min-height: min(100vh, 900px);
overflow: hidden;
display: flex;
align-items: flex-end;
justify-content: center;
padding: 48px 24px 54px;
background-color: #6b3e48;
background-image: url("https://community.robotime.com/uploads/default/original/3X/7/f/7fcf175aa66bdbd70f5ad20bd80c8f01cdae9582.jpeg");
background-position: center;
background-size: cover;
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.4);
}
.shade {
position: absolute;
inset: 0;
z-index: -1;
background:
linear-gradient(180deg, rgba(33, 16, 25, 0.03) 25%, rgba(33, 16, 25, 0.25) 58%, rgba(33, 16, 25, 0.82) 100%),
radial-gradient(circle at 50% 68%, transparent 0 24%, rgba(28, 14, 20, 0.18) 72%);
}
.brand-logo {
position: absolute;
z-index: 4;
top: clamp(20px, 4vw, 46px);
left: clamp(20px, 5vw, 68px);
width: clamp(92px, 12vw, 168px);
max-height: 74px;
object-fit: contain;
object-position: left center;
filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.18));
}
.content {
z-index: 3;
width: min(720px, 100%);
text-align: center;
text-shadow: 0 2px 14px rgba(58, 26, 37, 0.55);
}
.eyebrow {
margin: 0 0 7px;
color: var(--gold);
font-size: clamp(0.68rem, 1vw, 0.82rem);
font-weight: 800;
letter-spacing: 0.3em;
}
h1 {
margin: 0;
font-family: Georgia, "Times New Roman", serif;
font-size: clamp(2rem, 5vw, 4.35rem);
line-height: 1.02;
...
JavaScript
/* --------------------------------------------------------------------------
1) Replace these values with your Supabase project details.
Project URL: Supabase > Project Settings > Data API
Browser key: Supabase > Project Settings > API Keys > Publishable key
-------------------------------------------------------------------------- */
const CONFIG = {
supabaseUrl: "https://snisuupgavqwiqxjyrkd.supabase.co",
supabasePublishableKey: "sb_publishable_T1QQU1RKue_n9dWda8E1pQ_cuzvgkcA",
counterId: "rolife-9th",
demoStartingCount: 0
};
const joinButton = document.querySelector("#joinButton");
const buttonText = document.querySelector("#buttonText");
const counterText = document.querySelector("#counterText");
const statusMessage = document.querySelector("#statusMessage");
const successCard = document.querySelector("#successCard");
const canvas = document.querySelector("#fireworks");
const ctx = canvas.getContext("2d");
const joinedKey = `rolife-anniversary-joined:${CONFIG.counterId}`;
const visitorKey = "rolife-anniversary-visitor-id";
const demoCountKey = `rolife-anniversary-demo-count:${CONFIG.counterId}`;
const formatter = new Intl.NumberFormat("en-US");
let currentCount = CONFIG.demoStartingCount;
let particles = [];
let animationFrame = null;
function databaseIsConfigured() {
return (
CONFIG.supabaseUrl.startsWith("https://") &&
!CONFIG.supabaseUrl.includes("YOUR_") &&
CONFIG.supabasePublishableKey &&
!CONFIG.supabasePublishableKey.includes("YOUR_")
);
}
function getVisitorId() {
let id = localStorage.getItem(visitorKey);
if (!id) {
id = crypto.randomUUID();
localStorage.setItem(visitorKey, id);
}
return id;
}
function hasJoined() {
return localStorage.getItem(joinedKey) === "true";
}
function setJoinedUI() {
joinButton.disabled = true;
buttonText.textContent = "You’re Celebrating With Us!";
}
function renderCount(count) {
currentCount =...