<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();

External CSS

  1. https://codepen.io/web-dot-dev/pen/XWqWYgB.css
  2. https://codepen.io/web-dot-dev/pen/ZExZWBQ.css

External JavaScript

  1. https://codepen.io/web-dot-dev/pen/XWqWYgB.js
  2. https://codepen.io/web-dot-dev/pen/ZExZWBQ.js