INTERACTION_075CSS
Reading Progress Highlight
A thin progress rule tracks how far the reader has moved.
Copy / LightView
Key terms in a paragraph brighten after the copy settles. Keywords at the same color and weight as surrounding copy before the trigger; raising keyword contrast and weight once 300ms after the paragraph finishes revealing.
/* Keyword Emphasis Animation — Key terms in a paragraph brighten after the copy settles. */
.keyword-emphasis-animation {
--dur: 380ms;
--ease: cubic-bezier(0.22, 1, 0.36, 1);
--stagger: 120ms;
will-change: transform, opacity;
}
.keyword-emphasis-animation > * {
opacity: 0;
transform: translateY(14px);
animation: keyword-emphasis-animation-in var(--dur) var(--ease) forwards;
animation-delay: calc(var(--i, 0) * var(--stagger));
}
/* keywords at the same color and weight as surrounding copy -> raising keyword contrast and weight */
@keyframes keyword-emphasis-animation-in {
to {
opacity: 1;
transform: none;
filter: none;
}
}
@media (prefers-reduced-motion: reduce) {
.keyword-emphasis-animation > * {
opacity: 1;
transform: none;
animation: none;
}
}import { useEffect, useRef } from "react";
/** Keyword Emphasis Animation — trigger: 300ms after the paragraph finishes revealing */
export function KeywordEmphasisAnimation({ children }: { children: React.ReactNode }) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
el.dataset["state"] = "in";
return;
}
const io = new IntersectionObserver(
([entry]) => {
if (entry?.isIntersecting) {
el.dataset["state"] = "in";
io.unobserve(el);
}
},
{ threshold: 0.2 },
);
io.observe(el);
return () => io.disconnect();
}, []);
return (
<div ref={ref} className="keyword-emphasis-animation" data-state="out">
{children}
</div>
);
}A thin progress rule tracks how far the reader has moved.
List items slide in with their markers drawing first.
Content blocks appear in stages as the reader advances.
Sentences brighten in turn so attention moves down the block.