Magic Scroll Text
Scroll-reactive headline where each word fades in as you move down the page.
Installation
Add this component to your project using the CLI:
npx vui-registry-cli-v1 add magic-textSource Code
'use client'
import React, { useRef, useMemo } from 'react'
import { motion, useScroll, useTransform, useSpring, useMotionValue } from 'framer-motion'
// --- ULTRA-SMOOTH TEXT REVEAL (UNTOUCHED) ---
const Word = ({
children,
progress,
range,
}: {
children: string
progress: any
range: [number, number]
}) => {
const opacity = useSpring(useTransform(progress, range, [0.05, 1]), {
stiffness: 40,
damping: 20,
})
const filter = useTransform(progress, range, ['blur(4px)', 'blur(0px)'])
return (
<span className="relative mr-[0.4em] inline-block">
<motion.span style={{ opacity, filter }} className="text-zinc-800 selection:bg-zinc-200">
{children}
</motion.span>
</span>
)
}
const SmoothDescription = ({ text, centered = false }: { text: string; centered?: boolean }) => {
const container = useRef(null)
const { scrollYProgress } = useScroll({ target: container, offset: ['start 0.9', 'end 0.4'] })
const words = useMemo(() => text.split(' '), [text])
return (
<div
ref={container}
className={`flex flex-wrap ${centered ? 'justify-center text-center' : 'justify-start text-left'} max-w-3xl mx-auto`}
>
{words.map((word, i) => {
const start = i / words.length
const end = start + 0.1
return (
<Word key={i} progress={scrollYProgress} range={[start, end]}>
{word}
</Word>
)
})}
</div>
)
}
// --- PREMIUM 3D IMAGE WITH SOFT COLORS ---
const EditorialImage3D = ({ url, className }: { url: string; className: string }) => {
const ref = useRef<HTMLDivElement>(null)
const x = useMotionValue(0)
const y = useMotionValue(0)
const rotateX = useSpring(useTransform(y, [-0.5, 0.5], [8, -8]), { stiffness: 100, damping: 30 })
const rotateY = useSpring(useTransform(x, [-0.5, 0.5], [-8, 8]), { stiffness: 100, damping: 30 })
return (
<div
className="perspective-1000 w-full"
onMouseMove={(e) => {
const rect = ref.current?.getBoundingClientRect()
if (rect) {
x.set((e.clientX - rect.left) / rect.width - 0.5)
y.set((e.clientY - rect.top) / rect.height - 0.5)
}
}}
onMouseLeave={() => {
x.set(0)
y.set(0)
}}
>
<motion.div
ref={ref}
style={{ rotateX, rotateY, transformStyle: 'preserve-3d' }}
className={`relative overflow-hidden bg-[#f3f3f1] p-3 shadow-sm border border-zinc-200/50 ${className}`}
>
<motion.img
src={url}
className="w-full h-full object-cover opacity-90 sepia-[0.15] saturate-[0.85] brightness-[1.02]"
style={{ translateZ: 20 }}
/>
<div className="absolute inset-0 bg-gradient-to-tr from-orange-100/10 to-transparent mix-blend-soft-light pointer-events-none" />
</motion.div>
</div>
)
}
export default function CosmicEditorial() {
const containerRef = useRef(null)
const { scrollYProgress } = useScroll({ target: containerRef })
const bgTextMove = useSpring(useTransform(scrollYProgress, [0, 1], ['0%', '-10%']), {
stiffness: 15,
damping: 25,
})
const BIO_1 = `The exploration of space stands as humanity's greatest odyssey. From the first primitive rockets to the interstellar voyagers of tomorrow, our journey into the cosmos is a testament to our insatiable curiosity and the indomitable human spirit.`
const BIO_2 = `In the silence of the void, we find not just stars, but our own reflection. Every mission into the unknown pushes the boundaries of physics, engineering, and imagination, revealing a universe far more vast and beautiful than we ever dared to dream.`
return (
<div ref={containerRef} className="relative bg-transparent w-full min-h-[200vh] overflow-x-hidden">
{/* IMPROVED BACKGROUND TEXT (Ghost Thin) */}
<div className="fixed inset-0 pointer-events-none z-0 flex items-center select-none opacity-[0.04]">
<motion.h2
style={{ WebkitTextStroke: '1px #000', x: bgTextMove }}
className="text-[20vw] font-black whitespace-nowrap text-transparent"
>
EXPLORATION_DISCOVERY_INFINITY
</motion.h2>
</div>
<main className="relative z-10 max-w-screen-xl mx-auto px-8">
{/* HERO - TOP ALIGNED EDITORIAL */}
<section className="min-h-[600px] flex flex-col pt-32 border-b border-zinc-200">
<div className="flex justify-between items-end mb-12">
<div className="space-y-2">
<p className="font-mono text-[9px] uppercase tracking-[0.5em] text-zinc-400">
Mission / Vol. 01
</p>
<h1 className="text-7xl md:text-9xl font-light tracking-tighter text-zinc-900">
Cosmic <br /> <span className="font-serif italic font-thin">Frontiers</span>
</h1>
</div>
<div className="hidden md:block max-w-[200px] text-right">
<p className="text-[10px] uppercase leading-relaxed tracking-widest text-zinc-400">
A journey into the unknown depths of the universe.
</p>
</div>
</div>
<EditorialImage3D
url="https://images.unsplash.com/photo-1451187580459-43490279c0fa?q=80&w=1200&auto=format&fit=crop"
className="w-full h-[50vh]"
/>
</section>
{/* STATS STRIP */}
<div className="py-12 border-b border-zinc-200 grid grid-cols-2 md:grid-cols-4 gap-8">
{[
['Known Galaxies', '2T+'],
['Observable Universe', '93B ly'],
['Star Systems', '100B+'],
['Dark Matter', '85%'],
].map(([label, val]) => (
<div key={label} className="text-center md:text-left">
<p className="text-[9px] uppercase tracking-widest text-zinc-400 mb-1">{label}</p>
<p className="text-2xl font-light text-zinc-800">{val}</p>
</div>
))}
</div>
{/* DUAL COLUMN NARRATIVE */}
<section className="py-40 grid grid-cols-12 gap-12 items-start">
<div className="col-span-12 md:col-span-5">
<p className="text-[10px] font-bold uppercase tracking-[0.4em] mb-12 text-zinc-300">
— Origin
</p>
<div className="text-lg leading-relaxed text-zinc-700">
<SmoothDescription text={BIO_1} />
</div>
</div>
<div className="col-span-12 md:col-span-6 md:col-start-7">
<EditorialImage3D
url="https://images.unsplash.com/photo-1446776811953-b23d57bd21aa?q=80&w=800&auto=format&fit=crop"
className="aspect-[4/3] w-full"
/>
</div>
</section>
{/* FULL WIDTH QUOTE & IMAGE */}
<section className="py-20 flex flex-col items-center">
<div className="max-w-2xl text-center mb-32">
<div className="text-xl md:text-2xl italic font-serif text-zinc-800">
<SmoothDescription centered text={BIO_2} />
</div>
</div>
<div className="grid grid-cols-12 w-full gap-4">
<div className="col-span-8 h-80">
<EditorialImage3D
url="https://images.unsplash.com/photo-1517976487492-5750f3195933?q=80&w=1200&auto=format&fit=crop"
className="w-full h-full"
/>
</div>
<div className="col-span-4 h-80 bg-zinc-900 flex items-center justify-center p-8">
<p className="text-white text-[10px] font-mono leading-loose tracking-widest uppercase text-center">
"Somewhere, something incredible is waiting to be known."
</p>
</div>
</div>
</section>
{/* FOOTER */}
<footer className="py-60 flex flex-col items-center border-t border-zinc-200 mt-40">
<h2 className="text-[10vw] font-black tracking-tighter text-zinc-900 uppercase opacity-10 mb-8">
Infinity.
</h2>
<div className="flex gap-8 text-[10px] font-mono uppercase tracking-widest text-zinc-400">
<span>Instagram</span>
<span>Archive</span>
<span>2026</span>
</div>
</footer>
</main>
{/* OVERLAYS */}
<div className="fixed inset-0 pointer-events-none z-[100] border-[10px] border-[#f9f9f7]" />
<div className="fixed inset-0 pointer-events-none z-[101] opacity-[0.02] bg-[url('https://grainy-gradients.vercel.app/noise.svg')]" />
<style jsx global>{`
.perspective-1000 {
perspective: 1200px;
}
`}</style>
</div>
)
}
Dependencies
framer-motion: latest
Props
Component property reference.
| Name | Type | Default | Description |
|---|---|---|---|
| text | string | Required | The sentence or paragraph to animate word by word. |
Most components here are inspired by outstanding libraries and creators in the ecosystem. I don’t claim to be the original author — this is my space for learning, rebuilding, and understanding great work at a deeper level.
I’m still a student of the craft, constantly studying the best and translating what I learn through my own perspective. Every piece reflects curiosity, respect for the community, and small creative touches that feel true to me.
I’ve done my best to credit inspirations properly. If anything is missing or inaccurate, I truly appreciate a message so it can be corrected with care.

