<div id="title">Move your cursor over the smiley's mouth to feed it.</div>
<div id="wakeup-area">
    <div id="container">
        <div id="smiley">
            <div id="face">
                <div class="left eye">
                    <div class="pupil"></div>
                    <div class="left eyelid"></div>
                </div>
                <div class="right eye">
                    <div class="pupil"></div>
                    <div class="right eyelid"></div>
                </div>
                <div id="mouth__catch-area">
                    <div id="mouth"></div>
                </div>
            </div>
        </div>
        <div id="shadow"></div>
    </div>
</div>
<svg id="virtual-mouse-cursor" version="1.1" viewBox="0 0 11.6 18.2" xmlns="http://www.w3.org/2000/svg"><path d="M0 16V0l11.6 11.6H4.8l-.4.1z" fill="#fff"/><path d="M9.1 16.7l-3.6 1.5L.8 7.1l3.7-1.5z" fill="#fff"/><path d="M2.83 9.393l1.845-.774 3.096 7.376-1.844.775z"/><path d="M1 2.4v11.2l3-2.9.4-.1h4.8z"/></svg>
@import url('https://fonts.googleapis.com/css2?family=Special+Elite&display=swap');

* {
    box-sizing: border-box;
}

html, body {
    margin: 0;
    padding: 0;
    width: 100vw;
    height: 100vh;
}

body {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    background: #080e1e;
    background: radial-gradient(circle, rgb(65, 102, 202) -60%, rgba(0, 0, 0, 0.8901960784313725) 75%);
    background: -webkit-radial-gradient(circle, rgb(65, 102, 202) -60%, rgba(0, 0, 0, 0.8901960784313725) 75%);
    background: -moz-radial-gradient(circle, rgb(65, 102, 202) -60%, rgba(0, 0, 0, 0.8901960784313725) 75%);
    background: -o-radial-gradient(circle, rgb(65, 102, 202) -60%, rgba(0, 0, 0, 0.8901960784313725) 75%);			
    overscroll-behavior: none;
    cursor: none;
}

#title {
    position: absolute;
    top: 0;
    text-align: center;
    color: #FFF;
    padding: 50px 0 10px 0;
    font-family: 'Special Elite', cursive;
    font-size: 1.5rem;
}

#wakeup-area {
    padding: 100px;
}

#wakeup-area.hover .eyelid {
    animation-name: eyelid-open;
    animation-duration: 0.25s;
    animation-timing-function: ease-out;
    animation-fill-mode: forwards;
}

#container {
    --size: calc(100vmin - (100vmin * 0.2));
    --max-size: 300px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    position: relative;
    width: var(--size);
    height: calc(var(--size) + (var(--size) * 0.2));
    max-width: var(--max-size);
    max-height: calc(var(--max-size) + (var(--max-size) * 0.2));
}

#shadow {
    width: 100%;
    height: 10%;
    margin-top: 10%;
    background: #1c1c1c;		    
    border-radius: 70%;
    filter: blur(3px);

    animation-name: animate-shadow;
    animation-duration: 1s;
    animation-direction: alternate;
    animation-iteration-count: infinite;
    animation-timing-function: ease-in-out;
    animation-play-state: running;
}

@keyframes animate-shadow {
    0% { transform: scale(1.0); }
    100% { transform: scale(0.8); }
}

#smiley {
    width: 100%;
    height: 80%;
    background: radial-gradient(circle, #ffff00, #aaaa00);
    border-radius: 50%;

    animation-name: float;
    animation-duration: 1s;
    animation-direction: alternate;
    animation-iteration-count: infinite;
    animation-timing-function: ease-in-out;
    animation-play-state: running;
}

@keyframes float {
    0% { transform: translateY(0%); }
    100% { transform: translateY(10%); }
}

#smiley.hover {
    animation-play-state: paused;
}

#smiley.hover ~ #shadow {
    animation-play-state: paused;
}

#smiley.sick {
    background: linear-gradient(180deg, #ffff00, #aaaa00, #00aa00, #004400);
    background-size: 400% 400%;
    animation: sick 5s linear 1 forwards;
    animation-play-state: running;
}

@keyframes sick {
    0% { background-position: 50% 0%; }
    100% { background-position: 50% 100%; }
}

#smiley.sick #mouth {
    animation: mouth-sick 3s linear 1 forwards, mouth-open 1s ease-in-out 6s 1 forwards;
    animation-play-state: running;
}

@keyframes mouth-sick {
    0% {
        height: 100%;
        border-top-color: transparent;
        border-bottom-color: #000;
        background-color: transparent;
    }
    50% {
        height: 0%;
        border-top-color: transparent;
        border-bottom-color: #000;
        background-color: transparent;
    }
    51% {
        height: 0%;
        border-top-color: #000;
        border-bottom-color: transparent;
        background-color: transparent;
    }
    100% {
        height: 100%;
        border-top-color: #000;
        border-bottom-color: transparent;
        background-color: transparent;
    }
}

#smiley.sick ~ #shadow {
    animation-play-state: paused;
}

#smiley #mouth {
    animation-name: mouth-close;
    animation-duration: 1s;
    animation-timing-function: ease-out;
    animation-fill-mode: forwards;
}

@keyframes mouth-close {
    100% {
        height: 100%;
        background-color: transparent;
        border-top-color: transparent;
    }
    15%{
        height: 0%;
        background-color: transparent;
        border-top-color: transparent;
    }
    6% {
        height: 0%;
        background-color: transparent;
        border-top-color: #000;
    }
    5%{
        height: 0%;
        background-color: #000;
    }
    0% {
        height: 100%;
        background-color: #000;
    }
}

#smiley.hover #mouth {
    animation-name: mouth-open;
    animation-duration: 0.5s;
    animation-timing-function: ease-in-out;
    animation-fill-mode: forwards;
}

@keyframes mouth-open {
    0% {
        height: 100%;
        background-color: transparent;
        border-top-color: inherit;
    }
    5% {
        height: 0%;
        background-color: transparent;
        border-top-color: transparent;
    }
    6% {
        height: 0%;
        background-color: transparent;
        border-top-color: #000;
    }
    15% {
        height: 0%;
        background-color: #000;
    }
    100% {
        height: 100%;
        background-color: #000;
    }
}

#face {
    position: relative;
    width: 100%;
    height: 100%;
}

.eye {
    position: absolute;
    top: 20%;
    width: 25%;
    height: 25%;
    border-radius: 50%;
    background: #fff;
    overflow: hidden;
}
.left.eye { left: 20%; }
.right.eye { right: 20%; }

.eyelid {
    width: 100%;
    height: 100%;
    top: 0;
    position:absolute;
    background: radial-gradient(circle at top, #ffff00, #aaaa00);
    border-radius: 50%;
    
    animation-name: eyelid-close;
    animation-duration: 2s;
    animation-timing-function: ease-out;
    animation-fill-mode: forwards;
}

@keyframes eyelid-open {
    0% { transform: translateY(0%); }
    100% { transform: translateY(-100%); }
}

@keyframes eyelid-close {
    0% { transform: translateY(-100%); }
    100% { transform: translateY(0%); }
}

.pupil { 
    background: #000;
    width: 40%;
    height: 40%;
    position: relative;
    left: 30%;
    top: 30%;
    border-radius: 50%;
}

#mouth__catch-area {
    position: relative;
    width: 50%;
    height: 25%;
    top: 60%;
    left: 25%;
    display: flex;
    justify-content: center;
    align-items: center;
}

#mouth__catch-area.hover #mouth {
    height: 0%;
    border-bottom-color: #000;
    background-color: transparent;
    animation-name: eating;
    animation-delay: 0.25s;
    animation-duration: 1s;
    animation-direction: alternate;
    animation-iteration-count: 7;
    animation-timing-function: linear;
}

@keyframes eating {
    0% { height: 50%; }
    100% { height: 100%; }
}

#mouth {
    width: 100%;
    height: 100%;
    border: 1vw solid transparent;
    border-radius: 50%;
    border-bottom-color: #000;
}

#virtual-mouse-cursor {
  width: 30px;
  top: 0;
  left: 0;
  position: fixed;
}
/* Javascript is used for eye-tracking and virtual cursor on touch devices */

const cursorElem = document.querySelector('#virtual-mouse-cursor');

const wakeupAreaElem = document.querySelector('#wakeup-area');
const mouthCatchAreaElem = document.querySelector('#mouth__catch-area');
const smileyElem = document.querySelector('#smiley');

const leftEyeElem = document.querySelector('.left.eye');
const rightEyeElem = document.querySelector('.right.eye');
const leftPupilElem = document.querySelector('.left.eye .pupil');
const rightPupilElem = document.querySelector('.right.eye .pupil');

let cursorMoved = false;
let cursorEaten = false;
let smileySick = false;
let wakeupAreaHit = false;
let mouthCatchAreaHit = false;
let smileyAreaHit = false;

var leftEyeArea, leftPupilArea, rightEyeArea, rightPupilArea;
var wakeupArea, mouthCatchArea, smileyArea;

function updateCursor(x, y) {
    cursorElem.style.transform = `translate(${x}px, ${y}px)`;
}

function hideCursor(e) {
    cursorElem.style.display = 'none';
}

function showCursor(e) {
    cursorElem.style.display = '';
}

function calcEyePos(eyeArea, pupilArea, cursorX, cursorY) {
    let eyeMx = eyeArea.x + (eyeArea.w / 2);
    let eyeMy = eyeArea.y + (eyeArea.h / 2);
    
    let dx = cursorX - eyeMx;
    let dy = cursorY - eyeMy;
    
    let R = Math.sqrt(dx * dx + dy * dy);
    
    let maxRx = (eyeArea.w - pupilArea.w) / 2;
    let maxRy = (eyeArea.h - pupilArea.h) / 2;
    let maxR = Math.min(maxRx, maxRy);
    
    let offsetX = (R < maxR) ? dx : (maxR / R) * dx;
    let offsetY = (R < maxR) ? dy : (maxR / R) * dy;
    
    return {offsetX, offsetY};
}

function updateEyeTracking(cursorX, cursorY) {    
    let leftEyePos = calcEyePos(leftEyeArea, leftPupilArea, cursorX, cursorY);
    leftPupilElem.style.transform = `translate(${leftEyePos.offsetX}px, ${leftEyePos.offsetY}px)`;

    let rightEyePos = calcEyePos(rightEyeArea, rightPupilArea, cursorX, cursorY);
    rightPupilElem.style.transform = `translate(${rightEyePos.offsetX}px, ${rightEyePos.offsetY}px)`;
}

function resetEyeTracking() {
    leftPupilElem.style.transform = '';
    leftPupilElem.style.transition = 'transform 0.5s linear';
    
    rightPupilElem.style.transform = '';
    rightPupilElem.style.transition = 'transform 0.5s linear';
}

function onMouseMove(evt) {
    const e = (evt.touches && evt.touches[0]) || evt;    
    x = e.clientX;
    y = e.clientY;
    updateCursor(x, y);
    
    if(!cursorMoved) {
        cursorMoved = true;
        showCursor(e);
    }
    
    if(cursorEaten) {
        hideCursor(e);
    }
    else {
        if(wakeupAreaHit)
            updateEyeTracking(x, y);
        
        hitTest(x, y);
    }
}
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('touchmove', function(e) {
    e.preventDefault();
    e.stopPropagation();
    onMouseMove(e);
}, {passive: false});

document.body.addEventListener('mouseenter', showCursor);
document.body.addEventListener('mouseleave', hideCursor);

function isMobileDevice() {
    return (typeof window.orientation !== "undefined") || (navigator.userAgent.indexOf('IEMobile') !== -1);
}

function getPosSize(element) {
    let xPosition = 0;
    let yPosition = 0;
    
    const elemRect = element.getBoundingClientRect();

    while(element)
    {
        xPosition += (element.offsetLeft - element.scrollLeft + element.clientLeft);
        yPosition += (element.offsetTop - element.scrollTop + element.clientTop);
        element = element.offsetParent;
    }
    return { x: xPosition, y: yPosition, w: elemRect.width, h: elemRect.height };
}

function updateHover(elem, hover) {
    const HOVER_CLASSNAME = 'hover';
    if(hover) {
        if(!elem.classList.contains(HOVER_CLASSNAME)) {
            elem.classList.add(HOVER_CLASSNAME);
        }
    }
    else {
        if(elem.classList.contains(HOVER_CLASSNAME))
        elem.classList.remove(HOVER_CLASSNAME);
    }
}

function hitTestArea(area, mouseX, mouseY) {
    if(mouseX >= area.x &&
       mouseX < (area.x + area.w) &&
       mouseY >= area.y &&
       mouseY < (area.y + area.h)
      ) {
        return true;
    }
    return false;
}

function hitTest(mouseX, mouseY) {
    wakeupAreaHit = hitTestArea(wakeupArea, mouseX, mouseY);
    mouthCatchAreaHit = hitTestArea(mouthCatchArea, mouseX, mouseY);
    smileyAreaHit = hitTestArea(smileyArea, mouseX, mouseY);
    
    updateHover(wakeupAreaElem, wakeupAreaHit);
    
    if(!smileySick) {
        updateHover(mouthCatchAreaElem, mouthCatchAreaHit);
        updateHover(smileyElem, smileyAreaHit);

        if(mouthCatchAreaHit) {
            cursorEaten = true;
            hideCursor();
            resetEyeTracking();
        }
    }
}

function updateElemPos() {
    leftEyeArea = getPosSize(leftEyeElem);
    leftPupilArea = getPosSize(leftPupilElem);

    rightEyeArea = getPosSize(rightEyeElem);
    rightPupilArea = getPosSize(rightPupilElem);

    wakeupArea = getPosSize(wakeupAreaElem);
    mouthCatchArea = getPosSize(mouthCatchAreaElem);
    smileyArea = getPosSize(smileyElem);
    
    //setDebugRect('0', wakeupArea.x, wakeupArea.y, wakeupArea.w, wakeupArea.h, '#00f');
    //setDebugRect('1', smileyArea.x, smileyArea.y, smileyArea.w, smileyArea.h, '#f00');
}

function onResize(e) {
    updateElemPos();
}

function initAnimListener() {
    const mouthElem = document.querySelector('#mouth');
    mouthElem.addEventListener('animationend', (e) => {
        if(cursorEaten) {
            setTimeout(() => {
                if(!smileyElem.classList.contains('sick')) {
                    smileyElem.classList.add('sick');
                    smileySick = true;
                    
                    if(smileyElem.classList.contains('hover')) {
                        smileyElem.classList.remove('hover');
                    }

                    if(mouthCatchAreaElem.classList.contains('hover')) {
                        mouthCatchAreaElem.classList.remove('hover');
                    }
                }
                else {
                    let animName = e.animationName;
                    if(animName === 'mouth-open') {
                        showCursor();
                        cursorEaten = false;
                        let mx = mouthCatchArea.x + (mouthCatchArea.w / 2);
                        let my = mouthCatchArea.y + (mouthCatchArea.h / 2);
                        cursorElem.style.transition = 'transform 0.5s ease-in';
                        cursorElem.style.transform = `translate(${mx}px, ${my}px)` + ' scale(0.1)';
                        setTimeout(() => { cursorElem.style.transform = `translate(${mx}px, ${my}px)` + ' scale(3)'; }, 100);
                        setTimeout(() => { cursorElem.style.transform = `translate(${mx}px, ${my}px)` + ' scale(1)'; }, 600);
                        setTimeout(() => { cursorElem.style.transition = ''; }, 1000);
                        setTimeout(() => { smileyElem.classList.remove('sick'); }, 2000);
                        setTimeout(() => { smileySick = false; }, 30000);
                    }
                }
            },1500);
        }
    })
}

function init() {
    const w = document.body.clientWidth;
    const h = document.body.clientHeight;
    
    //createDebugRect('0', 0, 0, 0, 0, '#00f');
    //createDebugRect('1', 0, 0, 0, 0, '#f00');
    //createDebugRect('2', 0, 0, 0, 0, '#f00');
    
    updateElemPos();
    updateCursor(w / 2, h / 10);
    if(!cursorMoved && !isMobileDevice())
        hideCursor();
    
    initAnimListener();
}
document.addEventListener('DOMContentLoaded', init);

window.addEventListener('resize', onResize);

function createDebugRect(e, x, y, w, h, c) {
    let elem = document.createElement('div');
    elem.classList.add('debugRect--' + e);
    
    elem.style.position = 'absolute';
    elem.style.left = x + 'px';
    elem.style.top = y + 'px';
    elem.style.width = w + 'px';
    elem.style.height = h + 'px';
    
    elem.style.border = '1px solid ' + c;
    
    document.body.appendChild(elem);
}

function setDebugRect(e, x, y, w, h, c) {
    let elem = document.querySelector('div.debugRect--' + e);    
    elem.style.left = x + 'px';
    elem.style.top = y + 'px';
    elem.style.width = w + 'px';
    elem.style.height = h + 'px';
    
    elem.style.borderColor = c;
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.