diff --git a/index.html b/index.html new file mode 100644 index 0000000..806eef6 --- /dev/null +++ b/index.html @@ -0,0 +1,19 @@ + + + + + CodePen - Impossible Checkbox v2 🐻 + + + + + + +
+ + + + + + + diff --git a/script.js b/script.js new file mode 100644 index 0000000..44c1425 --- /dev/null +++ b/script.js @@ -0,0 +1,302 @@ +const { + React: { useState, useRef, useEffect, Fragment }, + ReactDOM: { render }, + gsap: { + set, + to, + timeline, + utils: { random } } } = + +window; +const rootNode = document.getElementById('app'); +const armLimit = random(0, 3); +const headLimit = random(armLimit + 1, armLimit + 3); +const angerLimit = random(headLimit + 1, headLimit + 3); +const armDuration = 0.2; +const bearDuration = 0.25; +const checkboxDuration = 0.25; +const pawDuration = 0.1; + +const SOUNDS = { + ON: new Audio('https://assets.codepen.io/605876/switch-on.mp3'), + OFF: new Audio('https://assets.codepen.io/605876/switch-off.mp3'), + GROAN: new Audio('https://assets.codepen.io/605876/bear-groan.mp3') }; + +SOUNDS.GROAN.playbackRate = 2; + +const App = () => { + const [checked, setChecked] = useState(false); + const [count, setCount] = useState(1); + const bearRef = useRef(null); + const swearRef = useRef(null); + const armWrapRef = useRef(null); + const pawRef = useRef(null); + const armRef = useRef(null); + const bgRef = useRef(null); + const indicatorRef = useRef(null); + + const onHover = () => { + if (Math.random() > 0.5 && count > armLimit) { + to(bearRef.current, bearDuration / 2, { y: '40%' }); + } + }; + const offHover = () => { + if (!checked) { + to(bearRef.current, bearDuration / 2, { y: '100%' }); + } + }; + const onChange = () => { + if (checked) return; + setChecked(true); + }; + + useEffect(() => { + const grabBearTL = () => { + /** + * Different height translations for the bear elements + * + * Paw will go to scaleX 0.8 + * Arm scaleX goes down to 0.7 + * Arm wrap translates to 50% or 50px + * Bear goes 100% -> 40% -> 0 + */ + let bearTranslation; + if (count > armLimit && count < headLimit) { + bearTranslation = '40%'; + } else if (count >= headLimit) { + bearTranslation = '0%'; + } + const onComplete = () => { + setChecked(false); + setCount(count + 1); + }; + let onBearComplete = () => {}; + if (Math.random() > 0.5 && count > angerLimit) + onBearComplete = () => { + SOUNDS.GROAN.play(); + set(swearRef.current, { display: 'block' }); + }; + const base = armDuration + armDuration + pawDuration; + const preDelay = Math.random(); + const delay = count > armLimit ? base + bearDuration + preDelay : base; + const bearTL = timeline({ delay: Math.random(), onComplete }); + bearTL. + add( + count > armLimit ? + to(bearRef.current, { + duration: bearDuration, + onComplete: onBearComplete, + y: bearTranslation }) : + + () => {}). + + to( + armWrapRef.current, + { x: 50, duration: armDuration }, + count > armLimit ? preDelay : 0). + + to(armRef.current, { scaleX: 0.7, duration: armDuration }). + to(pawRef.current, { + duration: pawDuration, + scaleX: 0.8, + onComplete: () => set(swearRef.current, { display: 'none' }) }). + + to( + bgRef.current, + { + onStart: () => { + SOUNDS.OFF.play(); + }, + duration: checkboxDuration, + backgroundColor: '#aaa' }, + + delay). + + to( + indicatorRef.current, + { duration: checkboxDuration, x: '0%' }, + delay). + + to(pawRef.current, { duration: pawDuration, scaleX: 0 }, delay). + to( + armRef.current, + { duration: pawDuration, scaleX: 1 }, + delay + pawDuration). + + to( + armWrapRef.current, + { duration: armDuration, x: 0 }, + delay + pawDuration). + + to( + bearRef.current, + { duration: bearDuration, y: '100%' }, + delay + pawDuration); + + return bearTL; + }; + const showTimeline = () => { + timeline({ + onStart: () => SOUNDS.ON.play() }). + + to( + bgRef.current, + { duration: checkboxDuration, backgroundColor: '#2eec71' }, + 0). + + to(indicatorRef.current, { duration: checkboxDuration, x: '100%' }, 0). + add(grabBearTL(), checkboxDuration); + }; + if (checked) showTimeline(); + }, [checked, count]); + + return ( + React.createElement(Fragment, null, + React.createElement("div", { className: "bear__wrap" }, + React.createElement("div", { ref: swearRef, className: "bear__swear" }, "#@$%*!"), + + + React.createElement("svg", { + ref: bearRef, + className: "bear", + viewBox: "0 0 284.94574 359.73706", + preserveAspectRatio: "xMinYMin" }, + React.createElement("g", { id: "layer1", transform: "translate(-7.5271369,-761.38595)" }, + React.createElement("g", { + id: "g5691", + transform: "matrix(1.2335313,0,0,1.2335313,-35.029693,-212.83637)" }, + React.createElement("path", { + id: "path4372", + d: "M 263.90933,1081.4151 A 113.96792,96.862576 0 0 0 149.99132,985.71456 113.96792,96.862576 0 0 0 36.090664,1081.4151 l 227.818666,0 z", + style: { fill: '#784421', fillOpacity: 1 } }), + + React.createElement("path", { + id: "path5634", + d: "m 250.42825,903.36218 c 2e-5,66.27108 -44.75411,114.99442 -102.42825,114.99442 -57.674143,0 -98.428271,-48.72334 -98.428251,-114.99442 4e-6,-66.27106 40.754125,-92.99437 98.428251,-92.99437 57.67413,0 102.42825,26.72331 102.42825,92.99437 z", + style: { fill: '#784421', fillOpacity: 1 } }), + + React.createElement("path", { + id: "path5639", + d: "m 217,972.86218 c 2e-5,21.53911 -30.44462,42.00002 -68,42.00002 -37.55538,0 -66.000019,-20.46091 -66,-42.00002 0,-21.53911 28.44464,-36 66,-36 37.55536,0 68,14.46089 68,36 z", + style: { fill: '#e9c6af', illOpacity: 1 } }), + + React.createElement("path", { + id: "path5636", + d: "m 181.5,944.36218 c 0,8.28427 -20.59974,26.5 -32.75,26.5 -12.15026,0 -34.75,-18.21573 -34.75,-26.5 0,-8.28427 22.59974,-13.5 34.75,-13.5 12.15026,0 32.75,5.21573 32.75,13.5 z", + style: { fill: '#000000', fillOpacity: 1 } }), + + React.createElement("g", { id: "g5681" }, + React.createElement("ellipse", { + style: { fill: '#784421', fillOpacity: 1 }, + id: "path5657", + cx: "69", + cy: "823.07269", + rx: "34.5", + ry: "33.289474" }), + + React.createElement("path", { + style: { fill: '#e9c6af', fillOpacity: 1 }, + d: "M 69,47.310547 A 24.25,23.399124 0 0 0 44.75,70.710938 24.25,23.399124 0 0 0 64.720703,93.720703 c 0.276316,-0.40734 0.503874,-0.867778 0.787109,-1.267578 1.70087,-2.400855 3.527087,-4.666237 5.470704,-6.798828 1.943616,-2.132591 4.004963,-4.133318 6.179687,-6.003906 2.174725,-1.870589 4.461274,-3.611714 6.855469,-5.226563 2.394195,-1.614848 4.896019,-3.10338 7.498047,-4.46875 0.539935,-0.283322 1.133058,-0.500695 1.68164,-0.773437 A 24.25,23.399124 0 0 0 69,47.310547 Z", + id: "ellipse5659", + transform: "translate(0,752.36216)" })), + + + React.createElement("g", { transform: "matrix(-1,0,0,1,300,0)", id: "g5685" }, + React.createElement("ellipse", { + ry: "33.289474", + rx: "34.5", + cy: "823.07269", + cx: "69", + id: "ellipse5687", + style: { fill: '#784421', illOpacity: 1 } }), + + React.createElement("path", { + transform: "translate(0,752.36216)", + id: "path5689", + d: "M 69,47.310547 A 24.25,23.399124 0 0 0 44.75,70.710938 24.25,23.399124 0 0 0 64.720703,93.720703 c 0.276316,-0.40734 0.503874,-0.867778 0.787109,-1.267578 1.70087,-2.400855 3.527087,-4.666237 5.470704,-6.798828 1.943616,-2.132591 4.004963,-4.133318 6.179687,-6.003906 2.174725,-1.870589 4.461274,-3.611714 6.855469,-5.226563 2.394195,-1.614848 4.896019,-3.10338 7.498047,-4.46875 0.539935,-0.283322 1.133058,-0.500695 1.68164,-0.773437 A 24.25,23.399124 0 0 0 69,47.310547 Z", + style: { fill: '#e9c6af', fillOpacity: 1 } })), + + + React.createElement("ellipse", { + ry: "9.6790915", + rx: "9.2701159", + cy: "900.38916", + cx: "105.83063", + id: "path4368", + style: { fill: '#000000', fillOpacity: 1 } }), + + React.createElement("ellipse", { + style: { fill: '#000000', fillOpacity: 1 }, + id: "ellipse4370", + cx: "186.89894", + cy: "900.38916", + rx: "9.2701159", + ry: "9.6790915" }), + + count >= angerLimit && + React.createElement(Fragment, null, + React.createElement("path", { + id: "path4396", + d: "m 92.05833,865.4614 39.42665,22.76299", + style: { + stroke: '#000000', + strokeWidth: 4.86408424, + strokeLinecap: 'round', + strokeLinejoin: 'round', + strokeMiterlimit: 4, + strokeOpacity: 1 } }), + + + React.createElement("path", { + style: { + stroke: '#000000', + strokeWidth: 4.86408424, + strokeLinecap: 'round', + strokeLinejoin: 'round', + strokeMiterlimit: 4, + strokeOpacity: 1 }, + + d: "m 202.82482,865.4614 -39.42664,22.76299", + id: "path4400" })))))), + + + + + + + + React.createElement("div", { ref: armWrapRef, className: "bear__arm-wrap" }, + React.createElement("svg", { + ref: armRef, + className: "bear__arm", + viewBox: "0 0 250.00001 99.999997", + preserveAspectRatio: "xMinYMin" }, + React.createElement("g", { transform: "translate(868.57141,-900.93359)", id: "layer1" }, + React.createElement("path", { + style: { fill: '#784421', fillOpacity: 1 }, + d: "m -619.43416,945.05124 c 4.18776,73.01076 -78.25474,53.24342 -150.21568,52.94118 -82.38711,-0.34602 -98.92158,-19.44459 -98.92157,-47.05883 0,-27.61424 4.78794,-42.54902 73.82353,-42.54902 69.03559,0 171.43607,-30.93764 175.31372,36.66667 z", + id: "path4971" }), + + React.createElement("ellipse", { + style: { fill: '#e9c6af', fillOpacity: 1 }, + id: "path4974", + cx: "-683.02264", + cy: "950.98572", + rx: "29.910826", + ry: "29.414362" })))), + + + + + React.createElement("div", { ref: pawRef, className: "bear__paw" }), + React.createElement("div", { className: "mask" }), + React.createElement("div", { className: "checkbox", onMouseOver: onHover, onMouseOut: offHover }, + React.createElement("input", { type: "checkbox", onChange: onChange, checked: checked }), + React.createElement("div", { ref: bgRef, className: "checkbox__bg" }), + React.createElement("div", { ref: indicatorRef, className: "checkbox__indicator" })))); + + + +}; + +render(React.createElement(App, null), rootNode); \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..0c6aeee --- /dev/null +++ b/style.css @@ -0,0 +1,152 @@ +* { + box-sizing: border-box; + background: #947cb0; +} +body { + -webkit-box-align: center; + align-items: center; + display: -webkit-box; + display: flex; + -webkit-box-pack: center; + justify-content: center; + font-family: 'Arial', sans-serif; + min-height: 100vh; + padding: 0; + margin: 0; + overflow: hidden; +} +.mask { + position: fixed; + top: 50%; + left: 0; + right: 0; + bottom: 0; + background: #947cb0; +} +.bear { + width: 100%; + background: transparent; + -webkit-transform: translate(0, 100%); + transform: translate(0, 100%); +} +.bear__swear { + display: none; + position: absolute; + left: 105%; + top: 0; + background: #fff; + font-weight: bolder; + padding: 10px; + border-radius: 8px; +} +.bear__swear:before { + content: ''; + background: #fff; + position: absolute; + top: 90%; + right: 70%; + height: 30px; + width: 30px; + clip-path: polygon(0 100%, 100% 0, 50% 0); + -webkit-clip-path: polygon(0 100%, 100% 0, 50% 0); +} +.bear__wrap { + width: 100px; + left: 50%; + position: absolute; + top: 50%; + -webkit-transform: translate(-15%, -50%) rotate(5deg) translate(0, -75%); + transform: translate(-15%, -50%) rotate(5deg) translate(0, -75%); + background: transparent; +} +.bear__arm-wrap { + background: transparent; + position: fixed; + height: 30px; + width: 90px; + z-index: 4; + top: 50%; + left: 50%; + -webkit-transform: translate(0, -50%) rotate(0deg); + transform: translate(0, -50%) rotate(0deg); +} +.bear__arm { + background: transparent; + -webkit-transform-origin: left; + transform-origin: left; + position: absolute; + height: 100%; + width: 100%; + top: 50%; + left: 50%; + -webkit-transform: translate(-35%, -50%) scaleX(1); + transform: translate(-35%, -50%) scaleX(1); +} +.bear__paw { + background: #784421; + border-radius: 100%; + position: fixed; + height: 30px; + width: 30px; + z-index: 10; + top: 50%; + left: 50%; + -webkit-transform-origin: right; + transform-origin: right; + -webkit-transform: translate(80px, -15px) scaleX(0); + transform: translate(80px, -15px) scaleX(0); +} +.checkbox { + border-radius: 50px; + height: 100px; + position: fixed; + width: 200px; + z-index: 5; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); +} +.checkbox [type='checkbox'] { + cursor: pointer; + border-radius: 50px; + position: absolute; + outline: 0; + top: 0; + right: 0; + bottom: 0; + left: 0; + opacity: 0; + z-index: 10; + height: 100%; + width: 100%; + margin: 0; +} +.checkbox__bg { + background: #aaa; + border-radius: 50px; + height: 100%; + width: 100%; + z-index: 10; +} +.checkbox__indicator { + background: transparent; + height: 100%; + width: 50%; + border-radius: 100%; + position: absolute; + top: 0; + left: 0; +} +.checkbox__indicator:after { + content: ''; + border-radius: 100%; + height: 85%; + width: 85%; + background: #fff; + position: absolute; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); +} \ No newline at end of file