diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..9f04a71
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..04b7ea4
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/prj.iml b/.idea/prj.iml
new file mode 100644
index 0000000..d6ebd48
--- /dev/null
+++ b/.idea/prj.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/images/il_570xN.3812195829_4zqo.avif b/images/il_570xN.3812195829_4zqo.avif
new file mode 100644
index 0000000..897b21a
Binary files /dev/null and b/images/il_570xN.3812195829_4zqo.avif differ
diff --git a/images/washing-machine.svg b/images/washing-machine.svg
new file mode 100644
index 0000000..e1fece2
--- /dev/null
+++ b/images/washing-machine.svg
@@ -0,0 +1,13 @@
+
+
+
\ No newline at end of file
diff --git a/images/zmm20d38gb-fr-1500x1500 (1) (1).png b/images/zmm20d38gb-fr-1500x1500 (1) (1).png
new file mode 100644
index 0000000..ddaadc1
Binary files /dev/null and b/images/zmm20d38gb-fr-1500x1500 (1) (1).png differ
diff --git a/images/zwf8240sb5-1500x1500.avif b/images/zwf8240sb5-1500x1500.avif
new file mode 100644
index 0000000..80795fb
Binary files /dev/null and b/images/zwf8240sb5-1500x1500.avif differ
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..cd628ba
--- /dev/null
+++ b/index.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+ Document
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/script.js b/script.js
new file mode 100644
index 0000000..b0c022a
--- /dev/null
+++ b/script.js
@@ -0,0 +1,193 @@
+const prizes = [
+ {
+ text: "Microwave 10",
+ color: "hsl(197 30% 43%)",
+ reaction: "dancing",
+ image: "./images/zmm20d38gb-fr-1500x1500 (1) (1).png"
+ },
+ {
+ text: "Microwave 15",
+ color: "hsl(173 58% 39%)",
+ reaction: "shocked",
+ image: "./images/zmm20d38gb-fr-1500x1500 (1) (1).png"
+ },
+ {
+ text: "Microwave 20",
+ color: "hsl(43 74% 66%)",
+ reaction: "shocked",
+ image: "./images/zmm20d38gb-fr-1500x1500 (1) (1).png"
+ },
+ {
+ text: "Microwave 25",
+ color: "hsl(27 87% 67%)",
+ reaction: "shocked",
+ image: "./images/zmm20d38gb-fr-1500x1500 (1) (1).png"
+ },
+ {
+ text: "Microwave 30 pro",
+ color: "hsl(12 76% 61%)",
+ reaction: "dancing",
+ image: "./images/zmm20d38gb-fr-1500x1500 (1) (1).png"
+ },
+ {
+ text: "10% Off",
+ color: "hsl(350 60% 52%)",
+ reaction: "laughing",
+ image: "./images/zmm20d38gb-fr-1500x1500 (1) (1).png"
+ },
+ {
+ text: "10% Off",
+ color: "hsl(91 43% 54%)",
+ reaction: "laughing",
+ image: "./images/zmm20d38gb-fr-1500x1500 (1) (1).png"
+ },
+ {
+ text: "10% Off",
+ color: "hsl(140 36% 74%)",
+ reaction: "dancing",
+ image: "./images/zmm20d38gb-fr-1500x1500 (1) (1).png"
+ }
+ ];
+
+ const wheel = document.querySelector(".deal-wheel");
+ const spinner = wheel.querySelector(".spinner");
+ const trigger = wheel.querySelector(".btn-spin");
+ const ticker = wheel.querySelector(".ticker");
+ const reaper = wheel.querySelector(".grim-reaper");
+ const prizeSlice = 360 / prizes.length;
+ const prizeOffset = Math.floor(180 / prizes.length);
+ const spinClass = "is-spinning";
+ const selectedClass = "selected";
+ const spinnerStyles = window.getComputedStyle(spinner);
+ let tickerAnim;
+ let rotation = 0;
+ let currentSlice = 0;
+ let prizeNodes;
+
+ const createPrizeNodes = () => {
+ prizes.forEach(({ text, color, reaction, image }, i) => {
+ const rotation = ((prizeSlice * i) * -1) - prizeOffset;
+
+ spinner.insertAdjacentHTML(
+ "beforeend",
+ `
+ ${text}
+
+ `
+ );
+ });
+ };
+
+ const createConicGradient = () => {
+ spinner.setAttribute(
+ "style",
+ `background: conic-gradient(
+ from -90deg,
+ ${prizes
+ .map(({ color }, i) => `${color} 0 ${(100 / prizes.length) * (prizes.length - i)}%`)
+ .reverse()
+ }
+ );`
+ );
+ };
+
+ const setupWheel = () => {
+ createConicGradient();
+ createPrizeNodes();
+ prizeNodes = wheel.querySelectorAll(".prize");
+ };
+
+ const spinertia = (min, max) => {
+ min = Math.ceil(min);
+ max = Math.floor(max);
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+ };
+
+ const runTickerAnimation = () => {
+ // https://css-tricks.com/get-value-of-css-rotation-through-javascript/
+ const values = spinnerStyles.transform.split("(")[1].split(")")[0].split(",");
+ const a = values[0];
+ const b = values[1];
+ let rad = Math.atan2(b, a);
+
+ if (rad < 0) rad += (2 * Math.PI);
+
+ const angle = Math.round(rad * (180 / Math.PI));
+ const slice = Math.floor(angle / prizeSlice);
+
+ if (currentSlice !== slice) {
+ ticker.style.animation = "none";
+ setTimeout(() => ticker.style.animation = null, 10);
+ currentSlice = slice;
+ }
+
+ tickerAnim = requestAnimationFrame(runTickerAnimation);
+ };
+
+ const selectPrize = () => {
+ const selected = Math.floor(rotation / prizeSlice);
+ prizeNodes[selected].classList.add(selectedClass);
+ reaper.dataset.reaction = prizeNodes[selected].dataset.reaction;
+ };
+
+ trigger.addEventListener("click", () => {
+ if (reaper.dataset.reaction !== "resting") {
+ reaper.dataset.reaction = "resting";
+ }
+
+ trigger.disabled = true;
+ rotation = Math.floor(Math.random() * 360 + spinertia(2000, 5000));
+ prizeNodes.forEach((prize) => prize.classList.remove(selectedClass));
+ wheel.classList.add(spinClass);
+ spinner.style.setProperty("--rotate", rotation);
+ ticker.style.animation = "none";
+ runTickerAnimation();
+ });
+
+spinner.addEventListener("transitionend", () => {
+ cancelAnimationFrame(tickerAnim);
+ trigger.disabled = false;
+ trigger.focus();
+ rotation %= 360;
+ selectPrize();
+ wheel.classList.remove(spinClass);
+ spinner.style.setProperty("--rotate", rotation);
+});
+
+setupWheel();
+
+// Power
+const button = document.querySelector(".btn-spin");
+const powerRectangle = document.querySelector(".power-rectangle");
+
+let holdStartTime = 0;
+let intervalId = null;
+
+const updatePowerIndicator = () => {
+const maxHoldDuration = 2000;
+const holdDuration = Date.now() - holdStartTime;
+const power = Math.min(100, (holdDuration / maxHoldDuration) * 100);
+
+powerRectangle.style.width = `${power}%`;
+powerRectangle.style.backgroundColor = `rgba(0, 128, 0, ${power / 100})`;
+};
+
+button.addEventListener("mousedown", () => {
+holdStartTime = Date.now();
+powerRectangle.style.width = '0';
+powerRectangle.style.backgroundColor = 'rgba(0, 128, 0, 0)';
+
+intervalId = setInterval(updatePowerIndicator, 50);
+});
+
+button.addEventListener("mouseup", () => {
+clearInterval(intervalId);
+powerRectangle.style.width = '0';
+powerRectangle.style.backgroundColor = 'rgba(0, 128, 0, 0)';
+});
+
+button.addEventListener("mouseleave", () => {
+clearInterval(intervalId);
+powerRectangle.style.width = '0';
+powerRectangle.style.backgroundColor = 'rgba(0, 128, 0, 0)';
+});
diff --git a/styles.css b/styles.css
new file mode 100644
index 0000000..fc3f49d
--- /dev/null
+++ b/styles.css
@@ -0,0 +1,226 @@
+@import url("https://fonts.googleapis.com/css2?family=Girassol&display=swap");
+
+* {
+ box-sizing: border-box;
+}
+
+html,
+body {
+ height: 100%;
+}
+
+body {
+ display: grid;
+ place-items: center;
+ overflow: hidden;
+}
+
+.deal-wheel {
+ --size: clamp(250px, 80vmin, 700px);
+ --lg-hs: 0 3%;
+ --lg-stop: 50%;
+ --lg: linear-gradient(
+ hsl(var(--lg-hs) 0%) 0 var(--lg-stop),
+ hsl(var(--lg-hs) 20%) var(--lg-stop) 100%
+ );
+
+ position: relative;
+ display: grid;
+ grid-gap: calc(var(--size) / 20);
+ align-items: center;
+ grid-template-areas:
+ "spinner"
+ "trigger";
+ font-family: "Girassol", sans-serif;
+ font-size: calc(var(--size) / 21);
+ line-height: 1;
+ text-transform: lowercase;
+}
+
+.deal-wheel > * {
+ grid-area: spinner;
+}
+
+.deal-wheel .btn-spin {
+ grid-area: trigger;
+ justify-self: center;
+}
+
+.spinner {
+ position: relative;
+ display: grid;
+ align-items: center;
+ grid-template-areas: "spinner";
+ width: var(--size);
+ height: var(--size);
+ transform: rotate(calc(var(--rotate, 25) * 1deg));
+ border-radius: 50%;
+ box-shadow: inset 0 0 0 calc(var(--size) / 40) hsl(0deg 0% 0% / 0.06);
+}
+
+.spinner * {
+ grid-area: spinner;
+}
+
+.prize {
+ position: relative;
+ display: flex;
+ align-items: center;
+ padding: 0 calc(var(--size) / 6) 0 calc(var(--size) / 20);
+ width: 50%;
+ height: 50%;
+ transform-origin: center right;
+ transform: rotate(var(--rotate));
+ user-select: none;
+}
+
+.cap {
+ --cap-size: calc(var(--size) / 4);
+ position: relative;
+ justify-self: center;
+ width: var(--cap-size);
+ height: var(--cap-size);
+}
+
+/* Hide select dropdown from SVG import file */
+.cap select {
+ display: none;
+}
+
+.cap svg {
+ width: 100%;
+}
+
+.ticker {
+ position: relative;
+ left: calc(var(--size) / -15);
+ width: calc(var(--size) / 10);
+ height: calc(var(--size) / 20);
+ background: var(--lg);
+ z-index: 1;
+ clip-path: polygon(20% 0, 100% 50%, 20% 100%, 0% 50%);
+ transform-origin: center left;
+}
+
+.btn-spin {
+ color: hsl(0deg 0% 100%);
+ background: var(--lg);
+ border: none;
+ font-family: inherit;
+ font-size: inherit;
+ line-height: inherit;
+ text-transform: inherit;
+ padding: 0.9rem 2rem 1rem;
+ border-radius: 0.25rem;
+ cursor: pointer;
+ transition: opacity 200ms ease-out;
+}
+
+.btn-spin:focus {
+ outline-offset: 2px;
+}
+
+.btn-spin:active {
+ transform: translateY(1px);
+}
+
+.btn-spin:disabled {
+ cursor: progress;
+ opacity: 0.25;
+}
+
+/* Spinning animation */
+.is-spinning .spinner {
+ transition: transform 8s cubic-bezier(0.1, -0.01, 0, 1);
+}
+
+.is-spinning .ticker {
+ animation: tick 700ms cubic-bezier(0.34, 1.56, 0.64, 1);
+}
+
+@keyframes tick {
+ 40% {
+ transform: rotate(-12deg);
+ }
+}
+
+/* Selected prize animation */
+.prize.selected .text {
+ color: white;
+ animation: selected 800ms ease;
+}
+
+@keyframes selected {
+ 25% {
+ transform: scale(1.25);
+ text-shadow: 1vmin 1vmin 0 hsla(0 0% 0% / 0.1);
+ }
+ 40% {
+ transform: scale(0.92);
+ text-shadow: 0 0 0 hsla(0 0% 0% / 0.2);
+ }
+ 60% {
+ transform: scale(1.02);
+ text-shadow: 0.5vmin 0.5vmin 0 hsla(0 0% 0% / 0.1);
+ }
+ 75% {
+ transform: scale(0.98);
+ }
+ 85% {
+ transform: scale(1);
+ }
+}
+
+.deal-wheel .spinner .prize img {
+ width:50%;
+}
+.hold-indicator {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 10px;
+ height: 100px;
+ background-color: rgba(0, 0, 0, 0.5);
+ border-radius: 5px;
+ display: none;
+ }
+
+ .indicator-bar {
+ position: absolute;
+ bottom: 0;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 100%;
+ height: 0;
+ background-color: #fff;
+ }
+
+ .arrow {
+ position: absolute;
+ top: -10px;
+ left: 50%;
+ transform: translateX(-50%);
+ border: solid transparent;
+ border-width: 10px;
+ border-bottom-color: #fff;
+ }
+
+ /* Power*/
+ .power-indicator {
+ position: relative;
+ width: 50%;
+ height: 100px;
+ margin-top: 10px;
+ background: #f0f0f0;
+ border: 1px solid #ccc;
+ overflow: hidden;
+ }
+
+ .power-rectangle {
+ width: 0;
+ height: 100%;
+ background: rgba(0, 128, 0, 0.7);
+ transition: width 0.1s, background-color 0.1s;
+ }
+
\ No newline at end of file