Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/apursley2012/log-portfolio/llms.txt

Use this file to discover all available pages before exploring further.

The Screensaver component replicates the classic Windows screensaver experience: leave the portfolio untouched for 60 seconds and a full-screen black overlay appears with the Windows logo and “Portfolio OS” text bouncing endlessly around the screen — just like a DVD player menu. Any interaction immediately dismisses it.

Idle Detection

The component listens for three browser events on window: mousemove, keydown, and click. Each event triggers the same handler, which both resets the active state to false (dismissing the screensaver if it’s showing) and clears then restarts a 60-second setTimeout:
const e = () => {
  d(false);               // setActive(false)
  clearTimeout(i);
  i = setTimeout(() => d(true), 6e4); // 60000ms
};

window.addEventListener("mousemove", e);
window.addEventListener("keydown", e);
window.addEventListener("click", e);
e(); // Start the timer immediately on mount
The handler is also called once on mount (e()) so the timer begins as soon as the component renders — no interaction needed to start counting.

Visual: The Full-Screen Overlay

When active is true, the component renders a full-screen black div with z-[999999] — above every other element in the application, including windows and Clippy:
<div className="fixed inset-0 bg-black z-[999999] overflow-hidden pointer-events-none">
pointer-events-none prevents the overlay itself from consuming mouse events, which ensures the mousemove listener on window still fires even while the screensaver is active, allowing instant dismissal.

Logo and Text

Inside the overlay, a Framer Motion motion.div contains the Windows logo (loaded from Wikimedia Commons) and the “Portfolio OS” label, both at 50% opacity for the authentic dim-screensaver look:
<motion.div
  className="absolute text-4xl font-bold text-white flex items-center gap-4"
  style={{ x: position.x, y: position.y }}
>
  <img
    src="https://upload.wikimedia.org/wikipedia/commons/e/e1/Windows_logo_-_1992.svg"
    alt="Logo"
    className="w-16 h-16 opacity-50"
  />
  <span className="opacity-50">Portfolio OS</span>
</motion.div>
The x and y values come from the position state (l), which is updated each animation frame by the bounce loop.

Bounce Physics

The bounce loop runs inside a second useEffect that only activates when active is true. It uses requestAnimationFrame to move the logo at a constant velocity and reverse direction when a wall is hit:
const size = { width: 200, height: 100 };

const loop = () => {
  setPosition(pos => {
    let nx = pos.x + velocity.x;
    let ny = pos.y + velocity.y;
    let vx = velocity.x;
    let vy = velocity.y;

    if (nx <= 0 || nx + size.width >= window.innerWidth) {
      vx = -velocity.x;
      nx = nx <= 0 ? 0 : window.innerWidth - size.width;
    }
    if (ny <= 0 || ny + size.height >= window.innerHeight) {
      vy = -velocity.y;
      ny = ny <= 0 ? 0 : window.innerHeight - size.height;
    }

    if (vx !== velocity.x || vy !== velocity.y) {
      setVelocity({ x: vx, y: vy });
    }
    return { x: nx, y: ny };
  });

  frameRef = requestAnimationFrame(loop);
};
The element is treated as a 200×100 pixel bounding box. When the leading edge would cross a wall boundary, the position is clamped and the corresponding velocity component is negated, producing the classic bounce. The default initial velocity is { x: 3, y: 3 } pixels per frame — roughly 180px/s at 60 fps.

Customizing the Screensaver

1

Change the idle timeout

Find the 6e4 literal in components/Screensaver.js and replace it with your preferred timeout in milliseconds:
i = setTimeout(() => d(true), 30000); // 30 seconds
2

Change the bounce speed

Locate the useState call that initialises velocity and increase or decrease the values:
const [t, h] = useState({ x: 5, y: 2 }); // faster horizontal, slower vertical
3

Swap the logo or text

Edit the JSX in components/Screensaver.js. Replace the <img> tag’s src with any image URL, and change the <span> text to whatever you like:
<img src="/my-logo.svg" alt="Logo" className="w-16 h-16 opacity-50" />
<span className="opacity-50">Jane Doe — Portfolio</span>
4

Adjust the element size

The bounce boundary check uses a hardcoded { width: 200, height: 100 } size. If you change the logo or text and the element grows or shrinks, update these values to match:
const size = { width: 300, height: 80 };
The screensaver overlay uses pointer-events-none, so clicks that dismiss it pass through to the desktop below. This means you can click a desktop icon to both dismiss the screensaver and open a window in one action.
The screensaver timer starts fresh on every page load. If you want it to persist across navigations — for example, while the user has left a tab open — keep the component mounted at the application root level (which it already is in the default setup).

Build docs developers (and LLMs) love