Focus pull

Lines of text come into focus at the centre of the screen and blur away at the edges.

How it works

For every line, the script measures how far its centre is from the viewport centre. That distance drives both blur and opacity, so only the line you are reading is sharp, like a camera pulling focus.

Full file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Focus pull</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; }
body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; -webkit-font-smoothing: antialiased; }

body { background: #0d1b2a; color: #f1f5f9; }
.poem { padding: 45vh 8vw; max-width: 760px; margin: 0 auto; }
.poem p {
  font: 600 clamp(1.8rem, 5vw, 3.2rem)/1.15 Georgia, serif;
  margin: 0 0 .9em; transition: filter .15s linear;
}
</style>
</head>
<body>
<main class="poem" id="poem">
  <p>Every page is a box.</p>
  <p>Most of it waits below the fold,</p>
  <p>patient, out of sight,</p>
  <p>until a thumb or a wheel</p>
  <p>pulls the next line up</p>
  <p>into the light.</p>
  <p>Scrolling is reading</p>
  <p>with your hands.</p>
  <p>The screen is a window,</p>
  <p>and the window moves.</p>
  <p>Keep going.</p>
  <p>There is always more box.</p>
</main>
<script>
const lines = document.querySelectorAll('.poem p');
function update() {
  const mid = innerHeight / 2;
  lines.forEach(l => {
    const r = l.getBoundingClientRect();
    const d = Math.abs(r.top + r.height / 2 - mid) / mid;
    l.style.filter = 'blur(' + (d * 10).toFixed(1) + 'px)';
    l.style.opacity = (1 - d * 0.85).toFixed(2);
  });
}
addEventListener('scroll', update, { passive: true });
update();
</script>
</body>
</html>

More reveal effects