Scroll revealsCC0-style snippets
Staggered list
A calm sequence for cards or list items, with the delay controlled by a CSS custom property.
[data-sc-effect="stagger"] [data-sc-stagger-item] {
opacity: 0;
transform: translateY(12px);
transition: opacity 450ms ease, transform 450ms ease;
transition-delay: calc(var(--sc-index, 0) * 70ms);
}
[data-sc-effect="stagger"].is-visible [data-sc-stagger-item] {
opacity: 1;
transform: none;
}
@media (prefers-reduced-motion: reduce) {
[data-sc-effect="stagger"] [data-sc-stagger-item] { opacity: 1; transform: none; transition: none; }
}
const staggerObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(({isIntersecting, target}) => {
if (!isIntersecting) return;
target.querySelectorAll('[data-sc-stagger-item]').forEach((item, index) => {
item.style.setProperty('--sc-index', index);
});
target.classList.add('is-visible');
observer.unobserve(target);
});
}, { threshold: 0.12 });
document.querySelectorAll('[data-sc-effect="stagger"]').forEach((el) => staggerObserver.observe(el));
Micro-interactionsCC0-style snippets
Spotlight hover
A pointer-following radial highlight that adds depth without a heavy visual layer.
[data-sc-effect="spotlight"] {
--sc-x: 50%;
--sc-y: 50%;
background: radial-gradient(circle at var(--sc-x) var(--sc-y), rgba(116, 125, 255, .22), transparent 34%), #141822;
transition: background 180ms ease, transform 180ms ease;
}
[data-sc-effect="spotlight"]:hover { transform: translateY(-2px); }
document.querySelectorAll('[data-sc-effect="spotlight"]').forEach((card) => {
card.addEventListener('pointermove', (event) => {
const box = card.getBoundingClientRect();
card.style.setProperty('--sc-x', `${event.clientX - box.left}px`);
card.style.setProperty('--sc-y', `${event.clientY - box.top}px`);
}, { passive: true });
});
Micro-interactionsCC0-style snippets
Magnetic button
A restrained pointer response for a primary CTA that remains keyboard and touch friendly.
[data-sc-effect="magnetic"] {
transition: transform 180ms cubic-bezier(.2, .8, .2, 1);
will-change: transform;
}
@media (prefers-reduced-motion: reduce) {
[data-sc-effect="magnetic"] { transition: none; }
}
document.querySelectorAll('[data-sc-effect="magnetic"]').forEach((button) => {
button.addEventListener('pointermove', (event) => {
const box = button.getBoundingClientRect();
const x = (event.clientX - (box.left + box.width / 2)) * .12;
const y = (event.clientY - (box.top + box.height / 2)) * .12;
button.style.transform = `translate(${x}px, ${y}px)`;
}, { passive: true });
button.addEventListener('pointerleave', () => { button.style.transform = ''; });
});
NavigationCC0-style snippets
Scroll progress line
A single fixed progress line that gives long-form pages a clear sense of place.
<div data-sc-effect="scroll-progress" aria-hidden="true"></div>
[data-sc-effect="scroll-progress"] {
position: fixed; inset: 0 auto auto 0; z-index: 10;
width: 100%; height: 3px; transform: scaleX(0); transform-origin: left;
background: #5b5ce2; pointer-events: none;
}
const progress = document.querySelector('[data-sc-effect="scroll-progress"]');
let progressTicking = false;
window.addEventListener('scroll', () => {
if (progressTicking) return;
progressTicking = true;
requestAnimationFrame(() => {
const max = document.documentElement.scrollHeight - window.innerHeight;
progress.style.transform = `scaleX(${max > 0 ? window.scrollY / max : 0})`;
progressTicking = false;
});
}, { passive: true });
BackgroundsCC0-style snippets
Cursor glow
A soft ambient glow for a hero or dark section that never blocks content or clicks.
[data-sc-effect="cursor-glow"] {
--sc-x: 50%; --sc-y: 50%;
background: radial-gradient(420px circle at var(--sc-x) var(--sc-y), rgba(91, 92, 226, .3), transparent 70%), #111318;
}
document.querySelectorAll('[data-sc-effect="cursor-glow"]').forEach((section) => {
section.addEventListener('pointermove', (event) => {
const box = section.getBoundingClientRect();
section.style.setProperty('--sc-x', `${event.clientX - box.left}px`);
section.style.setProperty('--sc-y', `${event.clientY - box.top}px`);
}, { passive: true });
});
BackgroundsCC0-style snippets
Animated gradient mesh
A low-frequency CSS background motion that gives a hero a little life without adding an image asset.
[data-sc-effect="gradient-mesh"] { position: relative; isolation: isolate; overflow: hidden; background: #111318; }
[data-sc-effect="gradient-mesh"]::before {
content: ""; position: absolute; inset: -35%; z-index: -1;
background: radial-gradient(circle at 20% 30%, #5b5ce2 0 12%, transparent 38%), radial-gradient(circle at 80% 70%, #9db7ff 0 8%, transparent 34%);
filter: blur(30px); opacity: .38; animation: sc-mesh 16s ease-in-out infinite alternate;
}
@keyframes sc-mesh { to { transform: translate3d(4%, -3%, 0) scale(1.08); } }
@media (prefers-reduced-motion: reduce) { [data-sc-effect="gradient-mesh"]::before { animation: none; } }
Text motionCC0-style snippets
Split text reveal
A small line-by-line headline reveal that avoids a runtime text-splitting dependency.
[data-sc-effect="split-text"] [data-sc-line] {
display: block; clip-path: inset(0 0 100% 0); transform: translateY(1em);
transition: clip-path 700ms cubic-bezier(.2, .8, .2, 1), transform 700ms cubic-bezier(.2, .8, .2, 1);
transition-delay: calc(var(--sc-index, 0) * 90ms);
}
[data-sc-effect="split-text"].is-visible [data-sc-line] { clip-path: inset(0); transform: none; }
@media (prefers-reduced-motion: reduce) {
[data-sc-effect="split-text"] [data-sc-line] { clip-path: inset(0); transform: none; transition: none; }
}
document.querySelectorAll('[data-sc-effect="split-text"]').forEach((heading) => {
heading.querySelectorAll('[data-sc-line]').forEach((line, index) => line.style.setProperty('--sc-index', index));
requestAnimationFrame(() => heading.classList.add('is-visible'));
});
Micro-interactionsCC0-style snippets
Hover lift card
A subtle elevation cue for clickable cards that keeps the hit area stable and the focus state visible.
[data-sc-effect="hover-lift"] {
transition: transform 180ms ease, box-shadow 180ms ease;
}
[data-sc-effect="hover-lift"]:hover,
[data-sc-effect="hover-lift"]:focus-visible {
transform: translateY(-5px);
box-shadow: 0 16px 30px rgba(17, 19, 24, .14);
}
@media (prefers-reduced-motion: reduce) {
[data-sc-effect="hover-lift"] { transition: none; }
}
Micro-interactionsCC0-style snippets
Button ripple
A contained click ripple that confirms activation without replacing the button label or focus ring.
[data-sc-effect="ripple"] { position: relative; overflow: hidden; isolation: isolate; }
[data-sc-effect="ripple"] .sc-ripple {
position: absolute; width: 12px; aspect-ratio: 1; border-radius: 50%;
background: currentColor; opacity: .22; transform: scale(0);
animation: sc-ripple 550ms ease-out forwards; pointer-events: none;
}
@keyframes sc-ripple { to { opacity: 0; transform: scale(18); } }
@media (prefers-reduced-motion: reduce) { [data-sc-effect="ripple"] .sc-ripple { display: none; } }
document.querySelectorAll('[data-sc-effect="ripple"]').forEach((button) => {
button.addEventListener('click', (event) => {
const box = button.getBoundingClientRect();
const ripple = document.createElement('span');
ripple.className = 'sc-ripple';
ripple.style.left = `${event.clientX - box.left - 6}px`;
ripple.style.top = `${event.clientY - box.top - 6}px`;
button.append(ripple);
ripple.addEventListener('animationend', () => ripple.remove(), { once: true });
});
});
ComponentsCC0-style snippets
Accordion disclosure
An accessible disclosure pattern with native button semantics and a restrained height transition.
<button type="button" aria-expanded="false" aria-controls="answer-1">What is included?</button>
<div id="answer-1" hidden>Short, useful supporting content.</div>
const trigger = document.querySelector('[aria-controls="answer-1"]');
const panel = document.getElementById(trigger.getAttribute('aria-controls'));
trigger.addEventListener('click', () => {
const open = trigger.getAttribute('aria-expanded') === 'true';
trigger.setAttribute('aria-expanded', String(!open));
panel.hidden = open;
});
ComponentsCC0-style snippets
Tooltip label
A compact contextual label for icon-only controls, shown on hover and keyboard focus.
[data-sc-tooltip] { position: relative; }
[data-sc-tooltip]::after {
content: attr(data-sc-tooltip); position: absolute; left: 50%; bottom: calc(100% + 8px);
transform: translate(-50%, 4px); opacity: 0; pointer-events: none; white-space: nowrap;
padding: 5px 7px; border-radius: 5px; background: #111318; color: #fff;
font: 12px/1.2 system-ui, sans-serif; transition: opacity 150ms ease, transform 150ms ease;
}
[data-sc-tooltip]:hover::after,
[data-sc-tooltip]:focus-visible::after { opacity: 1; transform: translate(-50%, 0); }
@media (prefers-reduced-motion: reduce) { [data-sc-tooltip]::after { transition: none; } }
NavigationCC0-style snippets
Active nav underline
A sliding active indicator that makes the current navigation context obvious.
[data-sc-effect="nav-underline"] { --sc-underline-x: 0%; --sc-underline-width: 0px; position: relative; }
[data-sc-effect="nav-underline"]::after {
content: ""; position: absolute; left: var(--sc-underline-x); bottom: -5px;
width: var(--sc-underline-width); height: 2px; background: currentColor;
transition: left 180ms ease, width 180ms ease;
}
@media (prefers-reduced-motion: reduce) { [data-sc-effect="nav-underline"]::after { transition: none; } }
const nav = document.querySelector('[data-sc-effect="nav-underline"]');
nav.querySelectorAll('a').forEach((link) => {
link.addEventListener('pointerenter', () => {
nav.style.setProperty('--sc-underline-x', `${link.offsetLeft}px`);
nav.style.setProperty('--sc-underline-width', `${link.offsetWidth}px`);
});
link.addEventListener('click', (event) => {
event.preventDefault();
nav.querySelectorAll('a').forEach((item) => item.removeAttribute('aria-current'));
link.setAttribute('aria-current', 'page');
});
});
ComponentsCC0-style snippets
Tabs switcher
A keyboard-friendly tab switcher with real selected state and a small content transition.
<div data-sc-effect="tabs-switcher">
<div role="tablist"><button type="button" role="tab" aria-selected="true" aria-controls="tab-a">A</button><button type="button" role="tab" aria-selected="false" aria-controls="tab-b">B</button></div>
<div id="tab-a" role="tabpanel">First view</div><div id="tab-b" role="tabpanel" hidden>Second view</div>
</div>
document.querySelectorAll('[data-sc-effect="tabs-switcher"] [role="tab"]').forEach((tab) => {
tab.addEventListener('click', () => {
const group = tab.closest('[role="tablist"]').parentElement;
group.querySelectorAll('[role="tab"]').forEach((item) => item.setAttribute('aria-selected', String(item === tab)));
group.querySelectorAll('[role="tabpanel"]').forEach((panel) => panel.hidden = panel.id !== tab.getAttribute('aria-controls'));
});
});
Media & layoutCC0-style snippets
Horizontal scroll cue
A touch-friendly horizontal rail with snap points and a visible cue that more content is available.
[data-sc-effect="horizontal-scroll"] {
display: flex; gap: 14px; overflow-x: auto; scroll-snap-type: x mandatory; overscroll-behavior-inline: contain;
scrollbar-width: thin;
}
[data-sc-effect="horizontal-scroll"] > * { flex: 0 0 min(78vw, 280px); scroll-snap-align: start; }
Text motionCC0-style snippets
Count-up number
A short numeric transition that gives a metric a sense of arrival without hiding the final value.
<span data-sc-effect="count-up" data-value="240">240</span>
const count = document.querySelector('[data-sc-effect="count-up"]');
const end = Number(count.dataset.value || 240);
const start = performance.now();
function tick(now) {
const progress = Math.min((now - start) / 900, 1);
count.textContent = Math.round(end * (1 - Math.pow(1 - progress, 3))).toLocaleString();
if (progress < 1) requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
Text motionCC0-style snippets
Typewriter text
A short, decorative typewriter cue for a headline or status line, with the full copy preserved for assistive technology.
<span data-sc-effect="typewriter" data-text="Build with clarity">Build with clarity</span>
const node = document.querySelector('[data-sc-effect="typewriter"]');
const text = node.dataset.text || node.textContent;
node.setAttribute('aria-label', text);
node.textContent = '';
let index = 0;
const type = () => { node.textContent = text.slice(0, ++index); if (index < text.length) setTimeout(type, 55); };
type();
Text motionCC0-style snippets
Pauseable marquee
A low-priority looping strip with a pause control, suitable for decorative keywords or partner labels.
[data-sc-effect="marquee"] { overflow: hidden; }
[data-sc-effect="marquee"] .sc-marquee-track { display: flex; width: max-content; animation: sc-marquee 18s linear infinite; }
[data-sc-effect="marquee"]:has(button:focus-visible) .sc-marquee-track,
[data-sc-effect="marquee"].is-paused .sc-marquee-track { animation-play-state: paused; }
@keyframes sc-marquee { to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) { [data-sc-effect="marquee"] .sc-marquee-track { animation: none; } }
<div data-sc-effect="marquee"><div class="sc-marquee-track"><span>Clarity · Motion · Speed · </span><span aria-hidden="true">Clarity · Motion · Speed · </span></div><button type="button" data-sc-marquee-toggle>Pause</button></div>
<script>
const marquee = document.querySelector('[data-sc-effect="marquee"]');
marquee.querySelector('[data-sc-marquee-toggle]').addEventListener('click', (event) => {
marquee.classList.toggle('is-paused');
event.currentTarget.textContent = marquee.classList.contains('is-paused') ? 'Play' : 'Pause';
});
</script>
BackgroundsCC0-style snippets
Shimmer skeleton
A restrained loading placeholder that communicates waiting without pretending content has arrived.
[data-sc-effect="shimmer"] { position: relative; overflow: hidden; background: #e8ecf4; }
[data-sc-effect="shimmer"]::after {
content: ""; position: absolute; inset: 0; transform: translateX(-100%);
background: linear-gradient(90deg, transparent, rgba(255,255,255,.7), transparent);
animation: sc-shimmer 1.5s ease-in-out infinite;
}
@keyframes sc-shimmer { to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { [data-sc-effect="shimmer"]::after { animation: none; } }
BackgroundsCC0-style snippets
Blob morph background
A soft organic background shape for a creative hero, kept behind content and disabled for reduced motion.
[data-sc-effect="blob-morph"] { position: relative; isolation: isolate; overflow: hidden; }
[data-sc-effect="blob-morph"]::before {
content: ""; position: absolute; width: 58%; aspect-ratio: 1; inset: 12% auto auto 22%; z-index: -1;
border-radius: 62% 38% 41% 59% / 53% 44% 56% 47%; background: #aab1ff; filter: blur(4px);
animation: sc-blob 9s ease-in-out infinite alternate;
}
@keyframes sc-blob { to { border-radius: 36% 64% 57% 43% / 41% 52% 48% 59%; transform: rotate(18deg) scale(1.08); } }
@media (prefers-reduced-motion: reduce) { [data-sc-effect="blob-morph"]::before { animation: none; } }
Text motionCC0-style snippets
Underline sweep
A bright underline that draws attention to a link while leaving the text and focus state intact.
[data-sc-effect="underline-sweep"] { background: linear-gradient(currentColor, currentColor) 0 100% / 0 2px no-repeat; transition: background-size 220ms ease; }
[data-sc-effect="underline-sweep"]:hover,
[data-sc-effect="underline-sweep"]:focus-visible { background-size: 100% 2px; }
@media (prefers-reduced-motion: reduce) { [data-sc-effect="underline-sweep"] { transition: none; } }
Media & layoutCC0-style snippets
Image zoom frame
A restrained image scale on hover that keeps the crop contained and does not alter the layout.
[data-sc-effect="image-zoom"] { overflow: hidden; }
[data-sc-effect="image-zoom"] img { display: block; width: 100%; transition: transform 350ms ease; }
[data-sc-effect="image-zoom"]:hover img,
[data-sc-effect="image-zoom"]:focus-within img { transform: scale(1.06); }
@media (prefers-reduced-motion: reduce) { [data-sc-effect="image-zoom"] img { transition: none; } }
Media & layoutCC0-style snippets
Pointer parallax layer
A small pointer-based depth shift for a decorative layer, with touch and reduced-motion fallbacks.
<div data-sc-effect="parallax"><div data-sc-parallax-layer>Focal visual</div></div>
<style>[data-sc-effect="parallax"] { overflow: hidden; } [data-sc-parallax-layer] { transition: transform 180ms ease; }</style>
<script>
const scene = document.querySelector('[data-sc-effect="parallax"]');
const layer = scene.querySelector('[data-sc-parallax-layer]');
scene.addEventListener('pointermove', (event) => {
const box = scene.getBoundingClientRect();
const x = (event.clientX - box.left) / box.width - .5;
const y = (event.clientY - box.top) / box.height - .5;
layer.style.transform = `translate3d(${x * 12}px, ${y * 12}px, 0)`;
}, { passive: true });
scene.addEventListener('pointerleave', () => { layer.style.transform = ''; });
ComponentsCC0-style snippets
Toast notification
A compact status message for a completed action, with live-region semantics and an explicit close affordance.
<button type="button" data-sc-toast-trigger>Save</button>
<div data-sc-effect="toast" role="status" hidden>Saved just now <button type="button" data-sc-toast-close aria-label="Close">×</button></div>
<script>
const toast = document.querySelector('[data-sc-effect="toast"]');
document.querySelector('[data-sc-toast-trigger]').addEventListener('click', () => {
toast.hidden = false;
window.setTimeout(() => { toast.hidden = true; }, 3200);
});
toast.querySelector('[data-sc-toast-close]').addEventListener('click', () => { toast.hidden = true; });
ComponentsCC0-style snippets
Toggle switch
A clear on/off control with a native checkbox underneath the visual switch.
<label class="switch"><input type="checkbox"><span aria-hidden="true"></span><b>Enable updates</b></label>
.switch { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; }
.switch input { position: absolute; opacity: 0; }
.switch span { width: 38px; height: 22px; border-radius: 999px; background: #cfd6e3; transition: background 180ms ease; }
.switch span::after { content: ""; display: block; width: 18px; height: 18px; margin: 2px; border-radius: 50%; background: #fff; transition: transform 180ms ease; }
.switch input:checked + span { background: #5b5ce2; }
.switch input:checked + span::after { transform: translateX(16px); }
.switch input:focus-visible + span { outline: 3px solid #aab1ff; outline-offset: 3px; }
Scroll revealsCC0-style snippets
Timeline draw
A vertical progress line that draws as milestones enter the viewport.
<div data-sc-effect="timeline"><i aria-hidden="true"></i><span data-sc-timeline-step>Plan</span><span data-sc-timeline-step>Build</span><span data-sc-timeline-step>Ship</span></div>
<style>[data-sc-effect="timeline"] { --sc-timeline-progress: 0%; position: relative; display: flex; gap: 16px; } [data-sc-effect="timeline"] > i { position: absolute; left: 0; right: 0; top: 50%; height: 2px; background: #d9deec; } [data-sc-effect="timeline"] > i::after { content: ""; display: block; width: var(--sc-timeline-progress); height: 100%; background: #5b5ce2; } [data-sc-effect="timeline"] > span { position: relative; padding: 5px; background: #fff; }</style>
<script>
const timeline = document.querySelector('[data-sc-effect="timeline"]');
const timelineObserver = new IntersectionObserver((entries) => entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
const visible = timeline.querySelectorAll('.is-visible').length;
const total = timeline.querySelectorAll('[data-sc-timeline-step]').length;
timeline.style.setProperty('--sc-timeline-progress', `${total > 1 ? ((visible - 1) / (total - 1)) * 100 : 100}%`);
}
}), { threshold: .35 });
timeline.querySelectorAll('[data-sc-timeline-step]').forEach((step) => timelineObserver.observe(step));
Micro-interactionsCC0-style snippets
Card flip reveal
A click-to-reveal two-sided card that works with keyboard focus and keeps both sides in the DOM.
[data-sc-effect="card-flip"] { perspective: 800px; }
[data-sc-effect="card-flip"] .sc-flip-inner { transition: transform 500ms ease; transform-style: preserve-3d; }
[data-sc-effect="card-flip"] .sc-flip-inner > * { position: absolute; inset: 0; display: grid; place-items: center; backface-visibility: hidden; }
[data-sc-effect="card-flip"] .sc-flip-inner > strong { transform: rotateY(180deg); }
[data-sc-effect="card-flip"].is-flipped .sc-flip-inner { transform: rotateY(180deg); }
@media (prefers-reduced-motion: reduce) { [data-sc-effect="card-flip"] .sc-flip-inner { transition: none; } }
<button type="button" data-sc-effect="card-flip" aria-pressed="false"><span class="sc-flip-inner"><b>Front</b><strong>Back</strong></span></button>
<script>
const flip = document.querySelector('[data-sc-effect="card-flip"]');
flip.addEventListener('click', () => { const open = flip.classList.toggle('is-flipped'); flip.setAttribute('aria-pressed', String(open)); });
</script>
Media & layoutCC0-style snippets
Scroll-snap gallery
A small gallery with native scroll snapping and button controls for users who prefer explicit navigation.
<div data-sc-effect="scroll-snap-gallery"><div class="sc-gallery-track"><article>01</article><article>02</article><article>03</article></div><button type="button" data-sc-gallery-prev>Previous</button><button type="button" data-sc-gallery-next>Next</button></div>
<style>[data-sc-effect="scroll-snap-gallery"] { overflow: hidden; } [data-sc-effect="scroll-snap-gallery"] .sc-gallery-track { display: flex; gap: 12px; overflow-x: auto; scroll-snap-type: x mandatory; } [data-sc-effect="scroll-snap-gallery"] article { flex: 0 0 78%; scroll-snap-align: start; }</style>
<script>
const rail = document.querySelector('[data-sc-effect="scroll-snap-gallery"]');
rail.querySelector('[data-sc-gallery-next]').addEventListener('click', () => rail.scrollBy({ left: rail.clientWidth * .8, behavior: 'smooth' }));
rail.querySelector('[data-sc-gallery-prev]').addEventListener('click', () => rail.scrollBy({ left: -rail.clientWidth * .8, behavior: 'smooth' }));
AOSMIT
AOS fade-up starter
A minimal, version-pinned Animate On Scroll setup for teams that want a maintained preset library.
<link rel="stylesheet" href="https://unpkg.com/aos@2.3.4/dist/aos.css">
<script src="https://unpkg.com/aos@2.3.4/dist/aos.js"></script>
<script>
document.querySelectorAll('[data-sc-effect="aos-fade-up"]').forEach((node) => node.setAttribute('data-aos', 'fade-up'));
AOS.init({ once: true, duration: 650, offset: 80, disable: window.matchMedia('(prefers-reduced-motion: reduce)').matches });
</script>
<article data-aos="fade-up">Your content</article>
MotionMIT
Motion spring entrance
A small modern Motion starter for a springy entrance when CSS transitions need more control.
<div data-sc-effect="motion-spring">Your content</div>
<script type="module">
import { animate } from 'https://cdn.jsdelivr.net/npm/motion@12.23.24/+esm';
const node = document.querySelector('[data-sc-effect="motion-spring"]');
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
animate(node, { opacity: [0, 1], y: [24, 0] }, reduceMotion ? { duration: 0 } : { type: 'spring', stiffness: 260, damping: 24 });
</script>
LenisMIT
Lenis smooth scroll
A smooth-scroll baseline for sites where scroll-linked motion is part of the interaction model.
<script type="module">
import Lenis from 'https://cdn.jsdelivr.net/npm/lenis@1.3.25/+esm';
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const wrapper = document.querySelector('[data-sc-effect="lenis-smooth-scroll"]') || window;
const lenis = new Lenis({ wrapper, autoRaf: !reduceMotion, anchors: true });
window.siteCommanderLenis = lenis;
if (reduceMotion) lenis.stop();
window.matchMedia('(prefers-reduced-motion: reduce)').addEventListener('change', ({matches}) => {
if (matches) lenis.stop(); else lenis.start();
});
</script>
Embla CarouselMIT
Embla carousel starter
A small, accessible carousel baseline with touch gestures and explicit previous/next controls.
<div class="embla"><div data-sc-effect="embla-carousel" class="embla__viewport"><div class="embla__container"><div class="embla__slide">Slide one</div><div class="embla__slide">Slide two</div></div></div><button type="button" data-embla-prev>Previous</button><button type="button" data-embla-next>Next</button></div>
<script type="module">
import EmblaCarousel from 'https://cdn.jsdelivr.net/npm/embla-carousel@8.5.2/+esm';
const viewport = document.querySelector('[data-sc-effect="embla-carousel"]');
const embla = EmblaCarousel(viewport, { loop: false });
document.querySelector('[data-embla-prev]').addEventListener('click', () => embla.scrollPrev());
document.querySelector('[data-embla-next]').addEventListener('click', () => embla.scrollNext());
</script>
Splitting.jsMIT
Splitting.js text starter
A character or word splitting starter for expressive headings that need precise sequencing.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/splitting@1.1.0/dist/splitting.css">
<script src="https://cdn.jsdelivr.net/npm/splitting@1.1.0/dist/splitting.min.js"></script>
<h2 data-sc-effect="splitting-text" data-splitting>Make it move</h2>
<style>[data-sc-effect="splitting-text"] .char { display: inline-block; opacity: 0; transform: translateY(.7em); animation: sc-split-in 650ms ease forwards; animation-delay: calc(var(--char-index, 0) * 60ms); } @keyframes sc-split-in { to { opacity: 1; transform: none; } } @media (prefers-reduced-motion: reduce) { [data-sc-effect="splitting-text"] .char { animation: none; opacity: 1; transform: none; } }</style>
<script>Splitting({ target: document.querySelector('[data-sc-effect="splitting-text"]'), by: 'chars' });</script>
TypewriterJSMIT
TypewriterJS starter
A pinned typewriter starter for short rotating phrases, with an accessible static fallback.
<span id="typed" data-sc-effect="typed-text" aria-label="Build with clarity">Build with clarity</span>
<script src="https://unpkg.com/typewriter-effect@2.22.0/dist/core.js"></script>
<script>
const node = document.querySelector('[data-sc-effect="typed-text"]');
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) node.textContent = 'Build with clarity';
else {
const typewriter = new Typewriter(node, { loop: true, delay: 48 });
typewriter.typeString('Build with clarity').pauseFor(1200).deleteAll().typeString('Ship with confidence').start();
}
</script>
CountUp.jsMIT
CountUp.js metric starter
A lightweight number animation starter for metrics that enter the viewport.
<span id="metric" data-sc-effect="countup-metric" data-value="240">240</span>
<script type="module">
import { CountUp } from 'https://cdn.jsdelivr.net/npm/countup.js@2.8.0/+esm';
const node = document.querySelector('[data-sc-effect="countup-metric"]');
if (!matchMedia('(prefers-reduced-motion: reduce)').matches) new CountUp(node, Number(node.dataset.value)).start();
</script>
VanillaTiltMIT
VanillaTilt card
A restrained 3D tilt starter for one featured card or product preview, with glare kept optional.
<script src="https://cdn.jsdelivr.net/npm/vanilla-tilt@1.8.1/dist/vanilla-tilt.min.js"></script>
<div data-sc-effect="vanilla-tilt">Featured visual</div>
<script>
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
VanillaTilt.init(document.querySelectorAll('[data-sc-effect="vanilla-tilt"]'), {
max: 7, speed: 500, scale: 1.015, glare: true, 'max-glare': .14
});
}
</script>