:root {
--bg: #070a12;
--text: #f0f4fb;
--panel: #121a2b;
--border: #263249;
--accent: #86e8ff;
--muted: #8a95a8;
--hue-hero: 0deg;
--hue: 0deg;
--hue-saturation: 80%;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.6;
overflow-x: hidden;
}
/* Hero Section */
.hero {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
background: linear-gradient(135deg, rgba(134, 232, 255, 0.05) 0%, rgba(174, 82, 255, 0.05) 100%);
}
.hero-content {
max-width: 700px;
text-align: center;
}
.eyebrow {
display: inline-block;
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 2px;
text-transform: uppercase;
color: var(--accent);
margin-bottom: 1rem;
}
.hero .gradient-text {
font-size: 3.5rem;
font-weight: 800;
line-height: 1.1;
margin-bottom: 1.5rem;
letter-spacing: -1px;
}
.subtitle {
font-size: 1.1rem;
color: var(--muted);
max-width: 500px;
margin: 0 auto;
}
/* Gradient Text Styling */
.gradient-text {
background: linear-gradient(
90deg,
hsl(var(--hue-hero), var(--hue-saturation), 50%),
hsl(calc(var(--hue-hero) + 60deg), var(--hue-saturation), 50%),
hsl(calc(var(--hue-hero) + 120deg), var(--hue-saturation), 50%)
);
background-size: 300% 100%;
background-position: 0% 50%;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
color: transparent;
animation: gradientShift 8s ease infinite;
}
/* Fallback for browsers that don't support gradient text */
@supports not (-webkit-background-clip: text) {
.gradient-text {
color: var(--accent);
background: none;
}
}
/* Spacer Sections */
.spacer {
padding: 6rem 2rem;
max-width: 800px;
margin: 0 auto;
border-bottom: 1px solid var(--border);
}
.spacer:nth-child(even) {
background: rgba(18, 26, 43, 0.5);
}
.spacer h2 {
font-size: 2rem;
margin-bottom: 1.5rem;
color: var(--text);
}
.spacer p {
font-size: 1rem;
color: var(--muted);
margin-bottom: 1.5rem;
line-height: 1.8;
}
.spacer ul {
margin-left: 1.5rem;
margin-bottom: 1.5rem;
}
.spacer li {
color: var(--muted);
margin-bottom: 0.8rem;
line-height: 1.7;
}
.spacer li strong {
color: var(--accent);
}
.spacer ol {
margin-left: 1.5rem;
margin-bottom: 1.5rem;
}
.spacer ol li {
color: var(--muted);
margin-bottom: 0.8rem;
line-height: 1.7;
}
code {
background: rgba(134, 232, 255, 0.1);
color: var(--accent);
padding: 0.2rem 0.5rem;
border-radius: 4px;
font-family: 'Monaco', 'Courier New', monospace;
font-size: 0.9rem;
}
/* Showcase Sections */
.showcase-1,
.showcase-2,
.showcase-3 {
padding: 6rem 2rem;
max-width: 800px;
margin: 0 auto;
background: linear-gradient(135deg, rgba(134, 232, 255, 0.03) 0%, rgba(174, 82, 255, 0.03) 100%);
border-top: 1px solid var(--border);
border-bottom: 1px solid var(--border);
}
.showcase-1 .gradient-text {
--hue: 0deg;
}
.showcase-2 .gradient-text {
--hue: 60deg;
}
.showcase-3 .gradient-text {
--hue: 120deg;
}
.showcase-1 h2,
.showcase-2 h2,
.showcase-3 h2 {
font-size: 2rem;
margin-bottom: 1.5rem;
}
.showcase-1 p,
.showcase-2 p,
.showcase-3 p {
font-size: 1rem;
color: var(--muted);
margin-bottom: 1.5rem;
line-height: 1.8;
}
/* Final Section */
.final-section {
max-width: 800px;
margin: 0 auto;
padding: 4rem 2rem;
text-align: center;
}
.btn-back {
display: inline-block;
margin-top: 1rem;
padding: 0.8rem 1.5rem;
background: var(--accent);
color: var(--bg);
border: none;
border-radius: 4px;
font-weight: 600;
text-decoration: none;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-back:hover {
background: rgba(134, 232, 255, 0.8);
transform: translateY(-2px);
}
.content {
max-width: 800px;
margin: 0 auto;
}
/* Animations */
@keyframes gradientShift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
/* Responsive */
@media (max-width: 768px) {
.hero .gradient-text {
font-size: 2.5rem;
}
.spacer h2,
.showcase-1 h2,
.showcase-2 h2,
.showcase-3 h2 {
font-size: 1.5rem;
}
.spacer,
.showcase-1,
.showcase-2,
.showcase-3 {
padding: 3rem 1.5rem;
}
}
@media (max-width: 480px) {
.hero .gradient-text {
font-size: 1.8rem;
}
.subtitle {
font-size: 1rem;
}
.spacer,
.showcase-1,
.showcase-2,
.showcase-3 {
padding: 2rem 1rem;
}
}
html.reduced-motion .gradient-text {
animation: none !important;
background-position: 0% 50% !important;
}
html.reduced-motion * {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
if (!window.MotionPreference) {
const __mql = window.matchMedia("(prefers-reduced-motion: reduce)");
const __listeners = new Set();
const MotionPreference = {
prefersReducedMotion() {
return __mql.matches;
},
setOverride(value) {
const reduced = Boolean(value);
document.documentElement.classList.toggle("reduced-motion", reduced);
window.dispatchEvent(new CustomEvent("motion-preference", { detail: { reduced } }));
for (const listener of __listeners) {
try {
listener({ reduced, override: reduced, systemReduced: __mql.matches });
} catch {}
}
},
onChange(listener) {
__listeners.add(listener);
try {
listener({
reduced: __mql.matches,
override: null,
systemReduced: __mql.matches,
});
} catch {}
return () => __listeners.delete(listener);
},
getState() {
return { reduced: __mql.matches, override: null, systemReduced: __mql.matches };
},
};
window.MotionPreference = MotionPreference;
}
function prefersReducedMotion() {
return window.MotionPreference.prefersReducedMotion();
}
function initDemoShell() {
// No-op shim in imported standalone snippets.
}
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import Lenis from 'lenis';
gsap.registerPlugin(ScrollTrigger);
initDemoShell({ title: 'Gradient Text with Scroll Hue Shift', category: 'scroll', tech: ['gsap', 'scrolltrigger', 'css-variables'] });
const lenis = new Lenis({ lerp: 0.1, smoothWheel: true });
lenis.on('scroll', ScrollTrigger.update);
gsap.ticker.add((time) => lenis.raf(time * 1000));
gsap.ticker.lagSmoothing(0);
const reduced = prefersReducedMotion();
if (reduced) document.documentElement.classList.add('reduced-motion');
// Intro animation
if (!reduced) {
gsap.set('.hero .eyebrow', { opacity: 0, y: 20 });
gsap.set('.hero .gradient-text', { opacity: 0, y: 40 });
gsap.set('.hero .subtitle', { opacity: 0, y: 25 });
gsap.timeline({ defaults: { ease: 'expo.out' } })
.to('.hero .eyebrow', { opacity: 1, y: 0, duration: 0.7, delay: 0.3 })
.to('.hero .gradient-text', { opacity: 1, y: 0, duration: 0.9 }, '-=0.4')
.to('.hero .subtitle', { opacity: 1, y: 0, duration: 0.7 }, '-=0.5');
}
// Hero gradient animation — synced to overall page scroll
if (!reduced) {
gsap.to(document.documentElement, {
'--hue-hero': 360,
duration: 1,
ease: 'none',
scrollTrigger: {
trigger: document.body,
start: 'top top',
end: 'bottom bottom',
scrub: 0.8,
onUpdate: (self) => {
// Optional: update opacity based on progress
const progress = self.progress;
document.documentElement.style.setProperty('--hue-saturation', `${80 + progress * 20}%`);
},
},
});
}
// Section-specific gradient animations
const gradientTexts = document.querySelectorAll('.gradient-text');
gradientTexts.forEach((el, index) => {
if (el.closest('.hero')) return; // Skip hero, already animated
// Initial state
gsap.set(el, { opacity: 0, y: 30 });
// Entrance animation
gsap.to(el, {
opacity: 1,
y: 0,
duration: 0.8,
ease: 'expo.out',
scrollTrigger: {
trigger: el,
start: 'top 75%',
toggleActions: 'play none none reverse',
},
});
// Section-specific hue shift
if (!reduced) {
const startHue = (index * 60) % 360;
const endHue = (startHue + 120) % 360;
gsap.to(el, {
'--hue': endHue,
duration: 1.2,
ease: 'sine.inOut',
scrollTrigger: {
trigger: el,
start: 'top 80%',
end: 'bottom 20%',
scrub: 1,
},
});
gsap.set(el, { '--hue': startHue });
}
});
// Stagger text content reveals
document.querySelectorAll('.spacer p, .spacer h2, .spacer ul, .showcase-1 p, .showcase-2 p, .showcase-3 p').forEach((el) => {
if (!reduced) {
gsap.set(el, { opacity: 0, y: 20 });
gsap.to(el, {
opacity: 1,
y: 0,
duration: 0.6,
ease: 'expo.out',
scrollTrigger: {
trigger: el,
start: 'top 75%',
toggleActions: 'play none none reverse',
},
});
}
});
// List items stagger
document.querySelectorAll('.spacer ul li').forEach((li, i) => {
if (!reduced) {
gsap.set(li, { opacity: 0, x: -20 });
gsap.to(li, {
opacity: 1,
x: 0,
duration: 0.6,
ease: 'expo.out',
delay: 0.05 * i,
scrollTrigger: {
trigger: li,
start: 'top 75%',
toggleActions: 'play none none reverse',
},
});
}
});
// Code and ol elements
document.querySelectorAll('.spacer ol, .spacer code, .showcase-1 code').forEach((el) => {
if (!reduced) {
gsap.set(el, { opacity: 0, y: 15 });
gsap.to(el, {
opacity: 1,
y: 0,
duration: 0.6,
ease: 'expo.out',
scrollTrigger: {
trigger: el,
start: 'top 75%',
toggleActions: 'play none none reverse',
},
});
}
});
// Motion preference listener
window.addEventListener('motion-preference', (e) => {
if (e.detail.reduced) {
gsap.globalTimeline.paused(true);
} else {
gsap.globalTimeline.paused(false);
}
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gradient Text with Scroll Hue Shift — stealthisdesign</title>
<link rel="stylesheet" href="style.css">
<script type="importmap">{"imports":{"gsap":"/vendor/gsap/index.js","gsap/ScrollTrigger":"/vendor/gsap/ScrollTrigger.js","gsap/SplitText":"/vendor/gsap/SplitText.js","gsap/Flip":"/vendor/gsap/Flip.js","gsap/ScrambleTextPlugin":"/vendor/gsap/ScrambleTextPlugin.js","gsap/TextPlugin":"/vendor/gsap/TextPlugin.js","gsap/all":"/vendor/gsap/all.js","gsap/":"/vendor/gsap/","lenis":"/vendor/lenis/dist/lenis.mjs","three":"/vendor/three/build/three.module.js","three/addons/":"/vendor/three/examples/jsm/"}}</script>
<style>html.lenis,
html.lenis body {
height: auto;
}
.lenis:not(.lenis-autoToggle).lenis-stopped {
overflow: clip;
}
.lenis [data-lenis-prevent],
.lenis [data-lenis-prevent-wheel],
.lenis [data-lenis-prevent-touch] {
overscroll-behavior: contain;
}
.lenis.lenis-smooth iframe {
pointer-events: none;
}
.lenis.lenis-autoToggle {
transition-property: overflow;
transition-duration: 1ms;
transition-behavior: allow-discrete;
}</style>
</head>
<body>
<section class="hero">
<div class="hero-content">
<span class="eyebrow">Demo 37</span>
<h1 class="gradient-text">Gradient Text Hue Shift</h1>
<p class="subtitle">Text with animated gradient background that shifts hue and saturation as you scroll down the page. Pure CSS + GSAP ScrollTrigger.</p>
</div>
</section>
<section class="spacer">
<div class="content">
<h2>How It Works</h2>
<p>This effect uses CSS Custom Properties (CSS variables) to dynamically control the hue, saturation, and lightness of a gradient background applied to text.</p>
<p>As the user scrolls, GSAP ScrollTrigger updates the CSS variables in real-time, creating a smooth color shift that feels organic and modern.</p>
</div>
</section>
<section class="showcase-1">
<div class="content">
<h2 class="gradient-text">The Technique</h2>
<p>A heading element uses <code>background: linear-gradient(...)</code> with CSS variables for hue rotation. Each section has its own hue range.</p>
<p>The effect is simple but impactful: as you scroll past, the text color smoothly transitions through the color spectrum.</p>
</div>
</section>
<section class="spacer">
<div class="content">
<h2>Perfect For</h2>
<ul>
<li><strong>Hero sections:</strong> Draw attention with color-shifting headlines</li>
<li><strong>Section headers:</strong> Visual rhythm and engagement</li>
<li><strong>Feature callouts:</strong> Highlight key messages</li>
<li><strong>Portfolio pages:</strong> Modern, eye-catching typography</li>
<li><strong>Marketing sites:</strong> Brand color integration and movement</li>
</ul>
</div>
</section>
<section class="showcase-2">
<div class="content">
<h2 class="gradient-text">Advanced Variations</h2>
<p>You can layer multiple hue shifts, adjust saturation for desaturation on scroll, or synchronize the shift with other animations on the page.</p>
<p>The gradient direction can also be linked to scroll velocity for even more dynamism.</p>
</div>
</section>
<section class="spacer">
<div class="content">
<h2>Implementation</h2>
<ol>
<li>Define CSS variables for hue: <code>--hue: 0deg</code></li>
<li>Apply gradient to text: <code>background: linear-gradient(to right, hsl(var(--hue), 100%, 50%), ...)</code></li>
<li>Use <code>background-clip: text</code> to show only the gradient on text</li>
<li>Update <code>--hue</code> with ScrollTrigger's <code>onUpdate</code> callback</li>
<li>Wrap in <code>prefers-reduced-motion</code> check for accessibility</li>
</ol>
</div>
</section>
<section class="showcase-3">
<div class="content">
<h2 class="gradient-text">Responsive & Accessible</h2>
<p>The effect gracefully degrades on browsers without gradient text support. When <code>prefers-reduced-motion</code> is enabled, the gradient is static or removed entirely.</p>
</div>
</section>
<section class="final-section">
<div class="content">
<p style="text-align: center; color: var(--muted);">Scroll back to the top to see the hero gradient shift again.</p>
<a href="/" class="btn-back">Back to Showcase</a>
</div>
</section>
<script type="module" src="script.js"></script>
</body>
</html>