diff --git a/index.html b/index.html
index 0fe2fba..bff65d6 100644
--- a/index.html
+++ b/index.html
@@ -9,14 +9,22 @@
Happy Button
-
- Total clicks: 0
- Clicks until celebration: 5
-
- Congratulations! You clicked the button 5 times.
+
+
+
+
+ Alternation sequence: 0 / 6
+
+ Alternate between both buttons to win.
+ Пасхалка ждет тебя!
+ Ты выйграл
+
+
diff --git a/script.js b/script.js
index 6291c97..adebf18 100644
--- a/script.js
+++ b/script.js
@@ -1,50 +1,121 @@
-const TOTAL_KEY = "happyButtonTotalClicks";
-const CYCLE_KEY = "happyButtonCelebrationClicks";
-const CELEBRATION_INTERVAL = 5;
+const SEQUENCE_KEY = "happyButtonAlternationSequence";
+const LAST_BUTTON_KEY = "happyButtonLastButton";
+const SEQUENCE_TARGET = 6;
+const EASTER_TARGET = 3;
const button = document.getElementById("happy-button");
+const secondButton = document.getElementById("second-button");
const clickStatus = document.getElementById("click-status");
const celebrationStatus = document.getElementById("celebration-status");
const celebrationMessage = document.getElementById("celebration-message");
+const easterHint = document.getElementById("easter-hint");
+const confetti = document.getElementById("confetti");
+const explosion = document.getElementById("explosion");
function readStoredCount(key) {
const value = Number.parseInt(window.localStorage.getItem(key) ?? "0", 10);
return Number.isFinite(value) && value >= 0 ? value : 0;
}
-let totalClicks = readStoredCount(TOTAL_KEY);
-let celebrationClicks = readStoredCount(CYCLE_KEY);
+let sequenceCount = Math.min(readStoredCount(SEQUENCE_KEY), SEQUENCE_TARGET);
+let lastButton = window.localStorage.getItem(LAST_BUTTON_KEY);
+let easterClicks = 0;
function renderState(showCelebration = false) {
- const isGreenButton = totalClicks % 2 === 0;
+ const hasStarted = sequenceCount > 0;
+ const isComplete = sequenceCount >= SEQUENCE_TARGET || showCelebration;
- button.classList.toggle("button-green", isGreenButton);
- button.classList.toggle("button-red", !isGreenButton);
- document.body.classList.toggle("state-green", isGreenButton);
- document.body.classList.toggle("state-red", !isGreenButton);
+ document.body.classList.toggle("state-green", !isComplete && lastButton !== "one");
+ document.body.classList.toggle("state-red", !isComplete && lastButton === "one");
+ document.body.classList.toggle("state-win", isComplete);
- clickStatus.textContent = `Total clicks: ${totalClicks}`;
- celebrationStatus.textContent = `Clicks until celebration: ${
- CELEBRATION_INTERVAL - celebrationClicks
- }`;
- if (showCelebration) {
- celebrationMessage.textContent = `Congratulations! You clicked the button ${totalClicks} times.`;
+ clickStatus.textContent = `Alternation sequence: ${sequenceCount} / ${SEQUENCE_TARGET}`;
+ celebrationStatus.textContent = hasStarted
+ ? `Next click: ${lastButton === "one" ? "Button 2" : "Button 1"}`
+ : "Alternate between both buttons to win.";
+ celebrationMessage.hidden = !isComplete;
+
+ if (isComplete) {
+ celebrationMessage.textContent = "Ты выйграл";
+ celebrationStatus.textContent = "Celebration unlocked!";
+ launchConfetti();
}
- celebrationMessage.hidden = !showCelebration;
}
-button.addEventListener("click", () => {
- totalClicks += 1;
- celebrationClicks += 1;
+function storeSequence() {
+ window.localStorage.setItem(SEQUENCE_KEY, String(sequenceCount));
+ if (lastButton) {
+ window.localStorage.setItem(LAST_BUTTON_KEY, lastButton);
+ }
+}
+
+function handleButtonClick(buttonName) {
+ if (!lastButton) {
+ sequenceCount = 1;
+ } else if (lastButton === buttonName) {
+ sequenceCount = 1;
+ } else {
+ sequenceCount += 1;
+ }
+
+ lastButton = buttonName;
- const shouldCelebrate = celebrationClicks === CELEBRATION_INTERVAL;
+ const shouldCelebrate = sequenceCount >= SEQUENCE_TARGET;
if (shouldCelebrate) {
- celebrationClicks = 0;
+ sequenceCount = SEQUENCE_TARGET;
}
- window.localStorage.setItem(TOTAL_KEY, String(totalClicks));
- window.localStorage.setItem(CYCLE_KEY, String(celebrationClicks));
+ storeSequence();
renderState(shouldCelebrate);
+}
+
+function launchConfetti() {
+ confetti.replaceChildren();
+ for (let index = 0; index < 24; index += 1) {
+ const piece = document.createElement("span");
+ piece.style.setProperty("--x", `${Math.random() * 240 - 120}px`);
+ piece.style.setProperty("--delay", `${Math.random() * 120}ms`);
+ piece.style.setProperty("--color", confettiColor(index));
+ confetti.append(piece);
+ }
+}
+
+function confettiColor(index) {
+ const colors = ["#f97316", "#22c55e", "#3b82f6", "#ec4899", "#eab308"];
+ return colors[index % colors.length];
+}
+
+function triggerExplosion() {
+ explosion.replaceChildren();
+ explosion.classList.remove("is-active");
+ for (let index = 0; index < 16; index += 1) {
+ const spark = document.createElement("span");
+ const angle = (index / 16) * Math.PI * 2;
+ spark.style.setProperty("--dx", `${Math.cos(angle) * 120}px`);
+ spark.style.setProperty("--dy", `${Math.sin(angle) * 120}px`);
+ explosion.append(spark);
+ }
+ window.requestAnimationFrame(() => {
+ explosion.classList.add("is-active");
+ });
+}
+
+function handleEasterHint() {
+ easterClicks += 1;
+ if (easterClicks >= EASTER_TARGET) {
+ easterClicks = 0;
+ triggerExplosion();
+ }
+}
+
+button.addEventListener("click", () => handleButtonClick("one"));
+secondButton.addEventListener("click", () => handleButtonClick("two"));
+easterHint.addEventListener("click", handleEasterHint);
+easterHint.addEventListener("keydown", (event) => {
+ if (event.key === "Enter" || event.key === " ") {
+ event.preventDefault();
+ handleEasterHint();
+ }
});
renderState(false);
diff --git a/style.css b/style.css
index a52a25b..4095a20 100644
--- a/style.css
+++ b/style.css
@@ -28,7 +28,13 @@ body.state-red {
background: #16a34a;
}
+body.state-win {
+ background: #fde047;
+}
+
.app {
+ position: relative;
+ overflow: hidden;
width: min(92vw, 28rem);
padding: 2rem;
text-align: center;
@@ -43,6 +49,12 @@ h1 {
line-height: 1;
}
+.button-row {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 0.75rem;
+}
+
.happy-button {
width: 100%;
min-height: 4rem;
@@ -80,6 +92,19 @@ h1 {
font-weight: 600;
}
+.hint {
+ margin: 1rem 0 0;
+ color: #4b5563;
+ cursor: pointer;
+ font-size: 0.95rem;
+ font-weight: 700;
+}
+
+.hint:focus-visible {
+ outline: 3px solid #111827;
+ outline-offset: 3px;
+}
+
.celebration {
margin: 1.25rem 0 0;
padding: 0.875rem 1rem;
@@ -90,6 +115,42 @@ h1 {
animation: celebrate-in 240ms ease-out;
}
+.confetti,
+.explosion {
+ pointer-events: none;
+ position: absolute;
+ inset: 0;
+ overflow: hidden;
+}
+
+.confetti span {
+ position: absolute;
+ top: 1rem;
+ left: 50%;
+ width: 0.6rem;
+ height: 0.9rem;
+ background: var(--color);
+ opacity: 0;
+ transform: translate(-50%, 0) rotate(0deg);
+ animation: confetti-drop 900ms ease-out var(--delay) forwards;
+}
+
+.explosion span {
+ position: absolute;
+ top: 58%;
+ left: 50%;
+ width: 0.75rem;
+ height: 0.75rem;
+ border-radius: 999px;
+ background: #f97316;
+ opacity: 0;
+ transform: translate(-50%, -50%) scale(0.4);
+}
+
+.explosion.is-active span {
+ animation: explode 620ms ease-out forwards;
+}
+
@keyframes celebrate-in {
from {
opacity: 0;
@@ -101,8 +162,39 @@ h1 {
}
}
+@keyframes confetti-drop {
+ 0% {
+ opacity: 1;
+ transform: translate(-50%, -1rem) rotate(0deg);
+ }
+ 100% {
+ opacity: 0;
+ transform: translate(calc(-50% + var(--x)), 11rem) rotate(260deg);
+ }
+}
+
+@keyframes explode {
+ 0% {
+ opacity: 1;
+ transform: translate(-50%, -50%) scale(0.4);
+ }
+ 100% {
+ opacity: 0;
+ transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy)))
+ scale(1.2);
+ }
+}
+
@media (prefers-reduced-motion: reduce) {
- .celebration {
+ .celebration,
+ .confetti span,
+ .explosion.is-active span {
animation: none;
}
}
+
+@media (max-width: 28rem) {
+ .button-row {
+ grid-template-columns: 1fr;
+ }
+}