All Prompts
All Prompts

testimonialquotescroll-animationtailwindinteractivemarketingsectionintersectionobserver
Interactive Quote Reveal Testimonial Section
Интерактивный блок отзывов с анимацией по словам при скролле. Аватар, бейдж доверия, темная тема. UI компонент для лендингов.
Prompt
<section class="relative z-10 max-w-7xl sm:px-6 lg:px-8 quoteRevealSection mr-auto ml-auto pt-8 pr-4 pb-20 pl-4" style="--reveal: 100%;">
<div class="relative overflow-hidden sm:p-10 ring-white/10 ring-1 bg-neutral-900 rounded-3xl pt-6 pr-6 pb-6 pl-6">
<!-- badge -->
<div class="flex justify-center">
<span class="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-3 py-1.5 text-[11px] text-neutral-200">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" data-lucide="star" class="lucide lucide-star w-3.5 h-3.5" style="stroke-width:1.5"><path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" class=""></path></svg>
TRUSTED BY CREATORS
</span>
</div>
<!-- quote with word-by-word reveal -->
<div class="relative mt-8 sm:mt-10 quoteReveal">
<p class="mx-auto max-w-5xl text-center sm:text-4xl md:text-5xl lg:text-6xl leading-[1.1] tracking-tight">
<span class="quote-word transition-colors duration-300" data-word-index="0" style="color: rgb(255, 255, 255);">"Vellum</span>
<span class="quote-word transition-colors duration-300" data-word-index="1" style="color: rgb(255, 255, 255);">Edit</span>
<span class="quote-word transition-colors duration-300" data-word-index="2" style="color: rgb(255, 255, 255);">has</span>
<span class="quote-word transition-colors duration-300" data-word-index="3" style="color: rgb(255, 255, 255);">completely</span>
<span class="quote-word transition-colors duration-300 font-instrument-serif italic" data-word-index="4" style="color: rgb(255, 255, 255);">transformed</span>
<span class="quote-word transition-colors duration-300" data-word-index="5" style="color: rgb(255, 255, 255);">our</span>
<span class="quote-word transition-colors duration-300" data-word-index="6" style="color: rgb(255, 255, 255);">workflow.</span>
<span class="quote-word transition-colors duration-300" data-word-index="7" style="color: rgb(255, 255, 255);">The</span>
<span class="quote-word transition-colors duration-300" data-word-index="8" style="color: rgb(255, 255, 255);">AI-powered</span>
<span class="quote-word transition-colors duration-300" data-word-index="9" style="color: rgb(255, 255, 255);">tools</span>
<span class="quote-word transition-colors duration-300" data-word-index="10" style="color: rgb(255, 255, 255);">and</span>
<span class="quote-word transition-colors duration-300" data-word-index="11" style="color: rgb(255, 255, 255);">real-time</span>
<span class="quote-word transition-colors duration-300" data-word-index="12" style="color: rgb(255, 255, 255);">performance</span>
<span class="quote-word transition-colors duration-300" data-word-index="13" style="color: rgb(255, 255, 255);">make</span>
<span class="quote-word transition-colors duration-300" data-word-index="14" style="color: rgb(255, 255, 255);">professional</span>
<span class="quote-word transition-colors duration-300" data-word-index="15" style="color: rgb(255, 255, 255);">editing</span>
<span class="quote-word transition-colors duration-300 font-instrument-serif italic" data-word-index="16" style="color: rgb(255, 255, 255);">accessible</span>
<span class="quote-word transition-colors duration-300" data-word-index="17" style="color: rgb(115, 115, 115);">to</span>
<span class="quote-word transition-colors duration-300" data-word-index="18" style="color: rgb(115, 115, 115);">everyone."</span>
</p>
</div>
<!-- author -->
<div class="sm:mt-10 flex gap-3 mt-8 items-center justify-center">
<img alt="Author avatar" class="h-10 w-10 ring-1 ring-white/10 object-cover rounded-full" src="https://hoirqrkdgbmvpwutwuwj-all.supabase.co/storage/v1/object/public/assets/assets/05d02a28-c159-471f-8c4d-f4df12e74bdf_320w.jpg">
<span class="text-sm sm:text-base text-neutral-300 font-medium">Creative Director & Editor</span>
</div>
<!-- subtle glow -->
</div>
<script>
(function () {
const section = document.querySelector('.quoteRevealSection');
const target = section ? section.querySelector('.quoteReveal') : null;
const words = section ? section.querySelectorAll('.quote-word') : [];
if (!section || !target || words.length === 0) return;
// Initialize all words as gray
words.forEach(word => {
word.style.color = 'rgb(115 115 115)'; // text-neutral-500
});
function updateWordReveal() {
const rect = target.getBoundingClientRect();
const vh = window.innerHeight || document.documentElement.clientHeight;
const viewportCenter = vh / 2;
// Calculate distance from element center to viewport center
const elementCenter = rect.top + rect.height / 2;
const distanceFromCenter = Math.abs(elementCenter - viewportCenter);
const maxDistance = vh / 2;
// Create a ratio where 0 = element is at viewport center, 1 = element is at edge
const centerRatio = Math.max(0, Math.min(1, 1 - (distanceFromCenter / maxDistance)));
// Calculate how many words to reveal based on center proximity
const totalWords = words.length;
const wordsToReveal = Math.floor(centerRatio * totalWords);
// Update word colors
words.forEach((word, index) => {
if (index < wordsToReveal) {
word.style.color = 'rgb(255 255 255)'; // text-white
} else {
word.style.color = 'rgb(115 115 115)'; // text-neutral-500
}
});
}
if (typeof IntersectionObserver !== 'undefined') {
const io = new IntersectionObserver(updateWordReveal, {
threshold: Array.from({ length: 101 }, (_, i) => i / 100)
});
io.observe(target);
}
window.addEventListener('scroll', updateWordReveal, { passive: true });
window.addEventListener('resize', updateWordReveal);
updateWordReveal();
})();
</script>
</section>