// An ISLAND: a single interactive widget hydrated inside an otherwise-static // page. The blog post page is `interactive: 'islands'`, so the framework // renders the article once and attaches React ONLY to this component — the // surrounding HTML never runs a React lifecycle. // // `hydrate: 'load'` wakes it immediately (it must track scrolling from the // first frame). Other strategies: 'idle', 'visible' (on scroll-into-view), // 'interaction' (on first pointer/key), 'never' (inert HTML forever). import type { ReactNode } from 'react' import { useEffect, useState } from 'react' import { island } from '@voltro/web' function ReadingProgress(): ReactNode { const [pct, setPct] = useState(0) useEffect(() => { const onScroll = (): void => { const el = document.documentElement const max = el.scrollHeight - el.clientHeight setPct(max > 0 ? Math.min(100, (el.scrollTop / max) * 100) : 0) } onScroll() window.addEventListener('scroll', onScroll, { passive: true }) window.addEventListener('resize', onScroll) return () => { window.removeEventListener('scroll', onScroll) window.removeEventListener('resize', onScroll) } }, []) return