<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>Inactivity Pop-up</h1>
  </header>
  <article>
    <p>
      <strong>
        In this demo, leave the page inactive for 4 seconds, and a pop-up will appear. Inactivity pop-ups are often seen in apps that display secure information. They often tell the user they'll get logged out if the inactivity continues.
      </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>
  </article>
</main>
<div id="inactivity-pop-up" popover>
  <div class="card elevated">
    <div class="title">You've barely touched your HTML</div>
    <div class="subtitle">It'll self-destruct in <span class="seconds"></span> seconds unless you interact.</div>
    <div class="actions">
      <button class="button filled ripple" popoverhidetarget="inactivity-pop-up">Keep it</button>
      <button class="button destructive ripple">Destroy it</button>
    </div>
  </div>
</div>
@layer demo {
  [popover] {
    transition: transform 0.2s;
    transform: scale(var(--open, 0));
    max-width: calc(100% - (2 * var(--size-4)));
    overflow: visible;
  }

  [popover]:open,
  [popover]:open::backdrop {
    --open: 1;
  }

  [popover]::backdrop {
    transition: opacity 0.2s;
    opacity: var(--open, 0);
    background: hsl(0 0% 10% / 0.5);
  }

  .seconds {
    font-weight: var(--font-weight-9);
  }

  .card {
    min-width: 0;
  }

  .card .actions {
    justify-content: flex-end;
    gap: 1ch;
  }

  .inactivity-ring {
    fill: none;
    position: fixed;
    top: var(--size-4);
    right: var(--size-4);
    color: var(--red-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;
  }

  .button.destructive {
    background: var(--md-sys-color-error);
    color: var(--md-sys-color-surface);
  }

  .button.destructive:is(:hover, :focus) {
    background: var(--red-6);
  }

  @keyframes load {
    to {
      stroke-dashoffset: 0;
    }
  }
}

@layer base {
  *,
  *:after,
  *:before {
    box-sizing: border-box;
    /*cursor: none;*/
    touch-action: none;
  }

  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: 25vmin;
    min-height: 200px;
    background: var(--gradient-2);
    display: grid;
    place-items: center;
    color: var(--gray-0);
    padding: var(--size-4);
    grid-template-columns: 1fr;
  }

  main {
    margin: 0 auto;
  }

  article {
    padding: var(--size-4);
    display: flex;
    flex-direction: column;
    align-items: center;
  }

  article > * + * {
    margin-top: var(--size-4);
  }
}
const POPUP = document.querySelector("#inactivity-pop-up");
const RING = document.querySelector("svg");
const BUTTON = document.querySelector("button.destructive");

const SCREENSAVER_THRESHOLD = 4000;
const DESTRUCTIVE_THRESHOLD = 9;

let checker;
let screensaverTimeout;
let secondsInterval;
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 destroy = () => {
  if (secondsInterval) clearInterval(secondsInterval);

  document.body.style = `
    display: grid;
    place-items: center;
  `;
  document.body.innerHTML = "<p>it's gone...</p>";

  if (screensaverTimeout) {
    clearTimeout(screensaverTimeout);
    document.body.classList.remove("timing");
  }

  EVENT_TYPES.forEach((e) =>
    document.body.removeEventListener(e, setSaverTimer)
  );
};

document.body.classList.add("timing");
document.documentElement.style.setProperty(
  "--threshold",
  SCREENSAVER_THRESHOLD
);

const SECONDS = document.querySelector(".seconds");

POPUP.addEventListener("beforetoggle", ({ newState }) => {
  if (newState === "closed") {
    if (POPUP.parentNode) setSaverTimer();
    if (secondsInterval) clearInterval(secondsInterval);
  } else {
    SECONDS.innerText = DESTRUCTIVE_THRESHOLD;
    secondsInterval = setInterval(() => {
      SECONDS.innerText -= 1;
      if (parseInt(SECONDS.innerText, 10) === 0) destroy();
    }, 1000);
  }
});

BUTTON.addEventListener("click", destroy);

setSaverTimer();

EVENT_TYPES.forEach((e) => document.body.addEventListener(e, 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