diff --git a/index.html b/index.html index 0fe2fba..652beee 100644 --- a/index.html +++ b/index.html @@ -9,11 +9,17 @@

Happy Button

- +
+ + +

Total clicks: 0

Clicks until celebration: 5

+

Sequence progress: 0 / 3

diff --git a/script.js b/script.js index 6291c97..079a6f5 100644 --- a/script.js +++ b/script.js @@ -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) { @@ -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; @@ -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; @@ -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); diff --git a/style.css b/style.css index a52a25b..1902a64 100644 --- a/style.css +++ b/style.css @@ -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 { @@ -28,6 +28,11 @@ body.state-red { background: #16a34a; } +body.state-celebrate { + background: #facc15; + color: #111827; +} + .app { width: min(92vw, 28rem); padding: 2rem; @@ -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; + } }