Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,17 @@
<body class="state-green">
<main class="app" aria-live="polite">
<h1>Happy Button</h1>
<button id="happy-button" type="button" class="happy-button button-green">
Click me
</button>
<div class="button-row">
<button id="happy-button" type="button" class="happy-button button-green">
Click me
</button>
<button id="happy-button-2" type="button" class="happy-button button-red">
Click me too
</button>
</div>
<p id="click-status" class="status">Total clicks: 0</p>
<p id="celebration-status" class="status">Clicks until celebration: 5</p>
<p id="sequence-status" class="status">Sequence progress: 0 / 3</p>
<p id="celebration-message" class="celebration" hidden>
Congratulations! You clicked the button 5 times.
</p>
Expand Down
91 changes: 87 additions & 4 deletions script.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
const TOTAL_KEY = "happyButtonTotalClicks";
const CYCLE_KEY = "happyButtonCelebrationClicks";
const CELEBRATION_INTERVAL = 5;
const SEQUENCE_TARGET = 3;
const SEQUENCE_LENGTH = SEQUENCE_TARGET * 2;

const button = document.getElementById("happy-button");
const button2 = document.getElementById("happy-button-2");
const clickStatus = document.getElementById("click-status");
const celebrationStatus = document.getElementById("celebration-status");
const sequenceStatus = document.getElementById("sequence-status");
const celebrationMessage = document.getElementById("celebration-message");

function readStoredCount(key) {
Expand All @@ -14,6 +18,12 @@ function readStoredCount(key) {

let totalClicks = readStoredCount(TOTAL_KEY);
let celebrationClicks = readStoredCount(CYCLE_KEY);
let sequenceStep = 0;
let sequenceCelebrationActive = false;

function alternationsForStep(step) {
return Math.floor(step / 2);
}

function renderState(showCelebration = false) {
const isGreenButton = totalClicks % 2 === 0;
Expand All @@ -27,13 +37,76 @@ function renderState(showCelebration = false) {
celebrationStatus.textContent = `Clicks until celebration: ${
CELEBRATION_INTERVAL - celebrationClicks
}`;
if (showCelebration) {
sequenceStatus.textContent = `Sequence progress: ${alternationsForStep(
sequenceStep,
)} / ${SEQUENCE_TARGET}`;

if (sequenceCelebrationActive) {
celebrationMessage.textContent = "Ты выйграл";
celebrationMessage.hidden = false;
} else if (showCelebration) {
celebrationMessage.textContent = `Congratulations! You clicked the button ${totalClicks} times.`;
celebrationMessage.hidden = false;
} else {
celebrationMessage.hidden = true;
}
}

function advanceSequence(clickedId) {
const expectedId = sequenceStep % 2 === 0 ? button.id : button2.id;
if (clickedId === expectedId) {
sequenceStep += 1;
} else if (clickedId === button.id) {
sequenceStep = 1;
} else {
sequenceStep = 0;
}

if (sequenceStep >= SEQUENCE_LENGTH) {
sequenceCelebrationActive = true;
sequenceStep = 0;
triggerSequenceCelebration();
document.body.dispatchEvent(new CustomEvent("sequence:complete"));
} else {
sequenceCelebrationActive = false;
document.body.classList.remove("state-celebrate");
}
}

const CONFETTI_COUNT = 80;
const CONFETTI_COLORS = ["#ef4444", "#22c55e", "#3b82f6", "#f59e0b", "#a855f7", "#ec4899"];
const CONFETTI_DURATION_MS = 2600;

function triggerSequenceCelebration() {
document.body.classList.add("state-celebrate");

const layer = document.createElement("div");
layer.className = "confetti-layer";
layer.setAttribute("aria-hidden", "true");

for (let i = 0; i < CONFETTI_COUNT; i += 1) {
const piece = document.createElement("span");
piece.className = "confetti-piece";
const color = CONFETTI_COLORS[i % CONFETTI_COLORS.length];
const left = Math.random() * 100;
const delay = Math.random() * 400;
const duration = 1800 + Math.random() * 800;
const rotate = Math.random() * 360;
piece.style.setProperty("--confetti-color", color);
piece.style.setProperty("--confetti-left", `${left}%`);
piece.style.setProperty("--confetti-delay", `${delay}ms`);
piece.style.setProperty("--confetti-duration", `${duration}ms`);
piece.style.setProperty("--confetti-rotate", `${rotate}deg`);
layer.appendChild(piece);
}
celebrationMessage.hidden = !showCelebration;

document.body.appendChild(layer);
window.setTimeout(() => {
layer.remove();
}, CONFETTI_DURATION_MS);
}

button.addEventListener("click", () => {
function handlePrimaryClick() {
totalClicks += 1;
celebrationClicks += 1;

Expand All @@ -42,9 +115,19 @@ button.addEventListener("click", () => {
celebrationClicks = 0;
}

advanceSequence(button.id);

window.localStorage.setItem(TOTAL_KEY, String(totalClicks));
window.localStorage.setItem(CYCLE_KEY, String(celebrationClicks));
renderState(shouldCelebrate);
});
}

function handleSecondaryClick() {
advanceSequence(button2.id);
renderState(false);
}

button.addEventListener("click", handlePrimaryClick);
button2.addEventListener("click", handleSecondaryClick);

renderState(false);
45 changes: 43 additions & 2 deletions style.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ body {
place-items: center;
color: #111827;
transition:
background-color 180ms ease,
color 180ms ease;
background-color 600ms ease,
color 600ms ease;
}

body.state-green {
Expand All @@ -28,6 +28,11 @@ body.state-red {
background: #16a34a;
}

body.state-celebrate {
background: #facc15;
color: #111827;
}

.app {
width: min(92vw, 28rem);
padding: 2rem;
Expand Down Expand Up @@ -101,8 +106,44 @@ h1 {
}
}

.confetti-layer {
position: fixed;
inset: 0;
pointer-events: none;
overflow: hidden;
z-index: 9999;
}

.confetti-piece {
position: absolute;
top: -10vh;
left: var(--confetti-left, 50%);
width: 0.6rem;
height: 1rem;
background: var(--confetti-color, #fbbf24);
transform: rotate(var(--confetti-rotate, 0deg));
opacity: 0.95;
animation: confetti-fall var(--confetti-duration, 2000ms) cubic-bezier(0.3, 0.7, 0.4, 1) var(--confetti-delay, 0ms) forwards;
}

@keyframes confetti-fall {
0% {
transform: translate3d(0, -10vh, 0) rotate(var(--confetti-rotate, 0deg));
opacity: 1;
}
100% {
transform: translate3d(0, 110vh, 0) rotate(calc(var(--confetti-rotate, 0deg) + 720deg));
opacity: 0.85;
}
}

@media (prefers-reduced-motion: reduce) {
.celebration {
animation: none;
}
.confetti-piece {
animation-duration: 0ms;
animation-delay: 0ms;
display: none;
}
}