<svg class="inactivity-ring" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="25" cy="25" r="20" stroke-width="10" stroke-linejoin="round" />
</svg>
<main>
<header>
<h1>screensaver.js</h1>
</header>
<article>
<p>
<strong>
In this demo, leave the page inactive for 4 seconds, and a screensaver will appear using a pop-up. On <code>show</code> it generates the animation behavior and leverages "light dismiss".
</strong>
</p>
<p>Lorem ipsum, dolor, sit amet consectetur adipisicing elit. Repellat illo eum hic possimus, libero voluptates, facere nihil deserunt similique error sunt vero minima enim cupiditate quis velit rem ab repellendus.</p>
<p>Neque hic veniam cum voluptates quae magnam quisquam saepe. Dolorum quasi amet officiis vero aliquid pariatur commodi repellendus. Quos iure nobis doloremque ipsum facere nostrum neque adipisci hic labore voluptates?</p>
<p>Mollitia beatae, repellat est laboriosam officiis? Earum veritatis incidunt explicabo id tempora perspiciatis non doloribus natus nisi asperiores quia error autem laudantium, amet doloremque, vitae quos. Soluta unde numquam id.</p>
<p>Recusandae facere esse quod dolorum maxime corrupti ex quam iure id quis eius cumque fugiat, consequatur autem, excepturi molestias libero velit illo a quo. Sed quae aut inventore quia, ipsa.</p>
<p>Eius id laboriosam, quas iure laborum corrupti optio corporis tempore odit laudantium similique, accusamus, vel! Perspiciatis sed saepe rem. Nihil explicabo ea at vitae rerum eum, corrupti ipsam suscipit, id!</p>
<p>Voluptate pariatur, cum praesentium, veniam tenetur distinctio totam voluptates ea beatae, dicta voluptatum illum sit! Earum harum odio natus perspiciatis. Magni eos temporibus harum quas laborum aspernatur, minus, sunt illum.</p>
<p>Ducimus nobis, libero illum commodi aliquid. Molestias beatae ducimus ratione quod, quis itaque ipsum, illum amet labore, nostrum quisquam enim. Sint aperiam qui nihil eaque id quam odio? Repudiandae, sint!</p>
<p>In rem tenetur accusamus accusantium id, officia quaerat atque, placeat architecto nihil unde, sit cupiditate minima tempore! Magni, atque labore. Dolor voluptatum eius incidunt, enim? Asperiores similique, ut amet atque?</p>
<p>Illum, quam fugiat, veniam officia totam nihil aut dolore quo! Officiis ipsa harum vero voluptatibus eaque repudiandae amet eligendi ducimus laboriosam ab maiores labore, odit, voluptas quos illo sint nulla.</p>
<p>Eius earum nihil officiis, inventore accusantium consequuntur dolor reprehenderit in esse quos eos iste nemo amet facere, mollitia, assumenda cum provident quam. Ipsam, vitae! Excepturi quos possimus enim, quia distinctio.</p>
<p>Lorem ipsum, dolor, sit amet consectetur adipisicing elit. Commodi eaque, ipsa voluptate quia deserunt mollitia pariatur harum, vero temporibus dolores, quibusdam rem provident sint? Architecto sequi, quasi sunt neque recusandae.</p>
</article>
</main>
<div id="screensaver" popover>
<div class="dvd">
<div class="dvd__scale">
<div class="dvd__slide">
<svg xmlns="http://www.w3.org/2000/svg" viewbox="8 44 178 104">
<g fill-rule="evenodd" clip-rule="evenodd">
<path d="M108.605 58.081c1.766-2.129 11.285-13.399 11.285-13.399h35.936c18.197 0 30.922 9.53 28.012 21.776-2.906 12.247-20.248 21.778-38.359 21.778h-24.09l7.227-30.422H145.6l-4.949 20.833h3.812c10.139 0 19.738-3.697 21.756-12.189 1.854-7.798-4.873-12.188-15.791-12.188h-3.984l-16.529.014-39.02 44.041-15.324-43.274s-.112-.276-.254-.649c-.035-.089-.328-.397-.485-.338-.286.106-.24.603-.19.711.125.266.162.367.196.497 1.368 3.34 1.446 8.946.89 11.187-3.031 12.218-20.25 21.778-38.359 21.778h-24.09l7.228-30.422H37.49l-4.949 20.833h3.812c10.138 0 19.682-3.697 21.699-12.189 1.852-7.798-4.817-12.188-15.734-12.188h-3.985l-16.983-.008 2.277-9.58H46.13l29.846-.039v.039h12.289s3.469 10.161 4.545 13.236c5.149 14.707 4.297 15.702 4.297 15.702s-.619-.949 11.498-15.54zM8.504 110.273c0-6.496 37.163-11.762 83.001-11.762 45.841 0 83.001 5.266 83.001 11.762 0 6.498-37.16 11.764-83.001 11.764-45.838 0-83.001-5.266-83.001-11.764zm79.997 4.153c10.47 0 18.954-1.754 18.954-3.922 0-2.164-8.484-3.92-18.954-3.92-10.467 0-18.952 1.756-18.952 3.92 0 2.168 8.485 3.922 18.952 3.922zm79.655 5.232h-.765l-.336 2.377h-.371l.332-2.377h-.764l.047-.338h1.902l-.045.338zm2.815 2.377h-.358v-2.058h-.008l-1.091 2.058-.469-2.078h-.008l-.67 2.078h-.357l.894-2.715h.311l.422 1.84.976-1.84h.358v2.715zM36.536 142.711h.062l5.119-10.32h4.816l-8.93 15.722h-2.622l-8.777-15.722h4.816l5.516 10.32zm25.447 4.996h-4.481v-15.316h4.481v15.316zm13.01-15.316h6.399c6.645 0 11.095 3.311 11.095 7.678 0 4.307-4.573 7.639-11.125 7.639h-6.37v-15.317h.001zm4.479 12.714h.731c5.517 0 7.651-2.031 7.651-5.059 0-3.33-2.562-5.057-7.651-5.057h-.731v10.116zm29.442-10.115v3.371h7.131v2.604h-7.131v4.14h7.406v2.602h-11.886v-15.316h11.886v2.599h-7.406zm19.041 5.037c0-4.287 4.662-8.045 11.367-8.045 6.703 0 11.369 3.758 11.369 8.045 0 4.387-4.666 8.086-11.369 8.086-6.705 0-11.367-3.699-11.367-8.086zm4.633-.119c0 2.312 2.926 5.26 6.734 5.26s6.734-2.947 6.734-5.26c0-2.479-2.803-4.979-6.734-4.979-3.933.001-6.734 2.501-6.734 4.979z"></path>
</g>
</svg>
</div>
</div>
</div>
</div>
@layer demo {
.dvd__scale {
position: fixed;
top: 0;
left: 0;
animation: dvd-scale calc(var(--duration) * 1s) calc(var(--delay) * -1s)
infinite linear alternate;
/*
For debugging the corner
animation-duration: 4s;
animation-delay: 0s;
*/
}
.dvd__slide {
animation: dvd-slide calc(var(--duration) * 1s) calc(var(--delay) * -1s)
infinite linear alternate;
/*
For debugging the corner
animation-duration: 4s;
animation-delay: 0s;
*/
}
#screensaver::backdrop {
background: hsl(0 0% 20% / 0.5);
backdrop-filter: blur(2px);
}
.dvd svg {
fill: hsl(var(--hue, 0) 80% 50%);
stroke: none;
width: clamp(2rem, 20vmin, 10rem);
}
@keyframes dvd-scale {
to {
transform: translateY(calc(100vh - 100%));
}
}
@keyframes dvd-slide {
to {
transform: translateX(calc(100vw - 100%));
}
}
}
@layer base {
*,
*:after,
*:before {
box-sizing: border-box;
}
body {
display: block;
min-height: 100vh;
font-family: "Google Sans", sans-serif, system-ui;
overflow: auto;
}
:where([popover]) {
margin: auto;
border-width: 0;
border-style: none;
background: transparent;
}
h1 {
margin: 0;
color: var(--gray-0);
}
header {
height: 35vmin;
min-height: 200px;
background: var(--gradient-16);
display: grid;
place-items: center;
color: var(--gray-0);
padding: var(--size-4);
grid-template-columns: 1fr;
}
h1 {
background: var(--gradient-18);
background-clip: text;
-webkit-background-clip: text;
background-attachment: fixed;
color: transparent;
font-size: var(--font-size-fluid-3);
}
main {
margin: 0 auto;
}
article {
padding: var(--size-4);
display: flex;
flex-direction: column;
align-items: center;
}
article > * + * {
margin-top: var(--size-4);
}
/* Inactivity ring */
.inactivity-ring {
fill: none;
position: fixed;
top: var(--size-4);
right: var(--size-4);
color: var(--teal-6);
width: 40px;
aspect-ratio: 1;
stroke-dasharray: 130;
stroke-dashoffset: 130;
transform: rotate(-90deg);
z-index: 20;
}
.inactivity-ring circle {
stroke: currentColor;
}
.timing .inactivity-ring {
animation: load calc(var(--threshold) * 1ms) linear;
}
@keyframes load {
to {
stroke-dashoffset: 0;
}
}
}
const randomInRange = (min, max) =>
Math.floor(
Math.random() * (Math.floor(max) - Math.ceil(min) + 1) + Math.ceil(min)
);
const POPUP = document.querySelector("#screensaver");
const RING = document.querySelector("svg");
const SCALE = document.querySelector(".dvd__scale");
const SLIDE = document.querySelector(".dvd__slide");
const DVD = document.querySelector(".dvd");
const MOVERS = [SLIDE, SCALE];
const SCREENSAVER_THRESHOLD = 4000;
const BOUNCE_THRESHOLD = 2; // ms between bounces for a cheer. Ideal is 0 for a corner.
let checker;
let screensaverTimeout;
const EVENT_TYPES = [
"pointermove",
"keypress",
"keydown",
"keyup",
"scroll",
"click"
];
const setSaverTimer = () => {
if (screensaverTimeout) {
clearTimeout(screensaverTimeout);
document.body.classList.remove("timing");
}
if (!POPUP.matches(":open")) {
screensaverTimeout = setTimeout(() => {
document.body.classList.remove("timing");
POPUP.showPopover();
}, SCREENSAVER_THRESHOLD);
requestAnimationFrame(() => {
document.body.classList.add("timing");
});
}
};
const BOUNCES = {
dvd__scale: 0,
dvd__slide: 0
};
const CHEER = new Audio(
"https://assets.codepen.io/605876/grunt-party--optimised.mp3"
);
const handleBounce = (e) => {
BOUNCES[e.target.className] = Date.now();
DVD.style.setProperty("--hue", randomInRange(0, 359));
const diff = Math.abs(BOUNCES.dvd__scale - BOUNCES.dvd__slide);
if (diff <= BOUNCE_THRESHOLD) CHEER.play();
};
POPUP.addEventListener("beforetoggle", ({ newState }) => {
if (newState === "closed") {
setSaverTimer();
} else {
DVD.style.setProperty("--hue", randomInRange(0, 359));
MOVERS.forEach((el) => {
const duration = randomInRange(2, 6);
el.style = `
--duration: ${duration};
--delay: ${duration * Math.random()};
`;
});
}
});
MOVERS.forEach((mover) =>
mover.addEventListener("animationiteration", handleBounce)
);
EVENT_TYPES.forEach((e) => document.body.addEventListener(e, setSaverTimer));
document.body.classList.add("timing");
document.documentElement.style.setProperty(
"--threshold",
SCREENSAVER_THRESHOLD
);
setSaverTimer();