Ten clean-room translations of the effects agents most often copy from React Bits, Magic UI, Aceternity, skiper-ui and motion-primitives. Every snippet here was built with next build (Next 16.3.5, React 19.2, Tailwind 4.3.3, @void/tokens precision direction), rendered in dark, light, reduced-motion and 390px, checked for real text in the server HTML with JS disabled, and scroll-measured with void smooth (verified 2026-09-16).
Rules shared by all ten
- Tokens only:
--brand,--brand-line,--fg,--fg-muted,--surface,--line,--bg,--radius-xl,--ease-out,--ease-in-out,--ease-out-expo,--duration-slow,--duration-slower. No hex, no palette classes. - Continuous motion uses only
transform/translate/opacity. Shiny text (paint) is the one exception, and it is limited to one short string. - Every infinite animation carries
data-fxand is paused offscreen byFxGate. Every effect has aprefers-reduced-motion: reduceframe that shows the finished state. - Server Components by default. Only
SpotlightCard,NumberTicker,MagneticandFxGateare client islands, each under 1 KB. - Decorative layers are
aria-hidden,pointer-events-none, absolutely positioned, and sit behind the content. Their parent needsrelative isolate. - These are independent implementations. No upstream source was copied, so they carry an "inspired by" courtesy comment and no licence obligation.
| # | Component | Inspired by | Tier / JS | Client? |
|---|---|---|---|---|
| 1 | Marquee |
Magic UI marquee, React Bits LogoLoop | T1 / 0 KB | no |
| 2 | SpotlightCard |
React Bits SpotlightCard, Magic UI magic-card | T2 / ~0.5 KB | yes |
| 3 | RevealText |
React Bits SplitText/BlurText, Aceternity text-generate-effect | T1 / 0 KB | no |
| 4 | BeamBorder |
Magic UI border-beam, React Bits StarBorder | T1 / 0 KB | no |
| 5 | PatternBackdrop (+ grain) |
Magic UI grid/dot-pattern, Aceternity background-beams | T0 / 0 KB | no |
| 6 | NumberTicker |
Magic UI number-ticker, React Bits CountUp | T2 / ~0.6 KB | yes |
| 7 | Magnetic |
React Bits Magnet, motion-primitives magnetic | T2 / ~0.5 KB | yes |
| 8 | ShinyText |
React Bits ShinyText, Magic UI animated-shiny-text | T1-paint / 0 KB | no |
| 9 | AuroraBackdrop |
React Bits Aurora/Silk (WebGL) | T1 / 0 KB | no |
| 10 | HeroSpotlight |
Aceternity spotlight | T1 one-shot / 0 KB | no |
Budget: 1 signature + 1 secondary per page (see SKILL.md). This page shows all ten only because it is a test bench.
0. Setup (once per project)
- Save each component to
src/components/fx/. They import.tsx cnfrom@/lib/cn, the template's class joiner. - Save the CSS blocks of the recipes you use to
src/styles/fx.css, then add@import "../styles/fx.css";as the last line ofsrc/app/globals.css, after the direction import. Only paste the blocks you use; each block is self-contained. - Mount
once insrc/app/layout.tsx, inside, after.
Header and shared pause rule for src/styles/fx.css:
/* fx.css: void signature effects. Import in globals.css AFTER the direction file.
Tokens only (--brand, --fg, --fg-muted, --surface, --line, --bg, --radius-*, --ease-*, --duration-*).
Classes live in @layer components so Tailwind utilities (p-6, rounded-2xl…) still override them. */
/* ── 0. Offscreen pause, toggled by <FxGate /> on every [data-fx] element ─────────────────── */
[data-fx-paused],
[data-fx-paused] *,
[data-fx-paused]::before,
[data-fx-paused]::after,
[data-fx-paused] ::before,
[data-fx-paused] ::after {
animation-play-state: paused !important;
}src/components/fx/fx-gate.tsx (a ~0.4 KB client island). Hidden tabs already stop CSS animations, so no visibilitychange listener is needed. For a single client effect you can wrap it in speed → PauseOffscreen instead; both mechanisms can coexist.
"use client";
import { usePathname } from "next/navigation";
import { useEffect } from "react";
/** Pauses CSS animations of every [data-fx] element while it is offscreen. Mount once in
* app/layout.tsx. Starts running; pauses only on evidence of being offscreen (no delayed entrances). */
export function FxGate() {
const pathname = usePathname();
useEffect(() => {
const els = document.querySelectorAll<HTMLElement>("[data-fx]");
if (els.length === 0) return;
const io = new IntersectionObserver(
(entries) => {
for (const e of entries) e.target.toggleAttribute("data-fx-paused", !e.isIntersecting);
},
{ rootMargin: "120px" },
);
els.forEach((el) => io.observe(el));
return () => io.disconnect();
}, [pathname]); // re-scan after client navigation
return null;
}Why start running and only pause on evidence of being offscreen: starting paused delays entrances until hydration.
1. Marquee (logo wall, testimonials)
- Inspired by: Magic UI
marquee(https://magicui.design/docs/components/marquee), React BitsLogoLoop(https://reactbits.dev). - Upstream bugs fixed: 4× duplicated children with no
aria-hidden/inert(links read and tabbed 4×); never pauses offscreen; no reduced motion; hover-only pause. - Tier: T1, 0 KB JS. Compositor
transformonly. - Reduced motion: no animation, the duplicate is removed, and the row becomes horizontally scrollable.
- A11y: a labelled
wrapping a. The duplicate track isaria-hidden+inert. The animation pauses on hover (fine pointers) and on:focus-within(keyboard).pausableadds a native checkbox pause (zero JS, WCAG 2.2.2) and is required for testimonials or any content that moves for more than 5s. Logo text must pass contrast: usetext-fg-muted, nottext-fg-subtle. (Historical: axe flagged 3.84:1 in the light theme before--fg-subtlewas raised to AA; muted is still the right tier for a row of logo wordmarks.) - SSR: a Server Component. Both tracks are in the HTML.
import type { CSSProperties, ReactNode } from "react";
import { cn } from "@/lib/cn";
type MarqueeProps = {
/** <li> elements. Logos: next/image with width/height + alt, equal visual weight. */
children: ReactNode;
/** Accessible name for the region, e.g. "Companies using Acme". */
label: string;
/** Seconds per loop. ≈ 40s per 1600px of content; never faster than ~60px/s. */
duration?: number;
reverse?: boolean;
/** Content that moves > 5s and carries information (testimonials) needs a pause control (WCAG 2.2.2). */
pausable?: boolean;
className?: string;
};
/** Server Component. Two tracks; the duplicate is aria-hidden + inert (no double reading, no double tab stops). */
export function Marquee({ children, label, duration = 40, reverse = false, pausable = false, className }: MarqueeProps) {
return (
<div className={cn("fx-marquee-wrap", className)}>
<section
aria-label={label}
data-fx="marquee"
data-reverse={reverse || undefined}
className="fx-marquee"
style={{ "--fx-marquee-duration": `${duration}s` } as CSSProperties}
>
<ul className="fx-marquee__track">{children}</ul>
<ul className="fx-marquee__track" aria-hidden="true" inert>
{children}
</ul>
</section>
{pausable ? (
<label className="mt-3 inline-flex cursor-pointer items-center gap-2 text-sm text-fg-muted">
<input type="checkbox" className="fx-marquee__pause" /> Pause scrolling
</label>
) : null}
</div>
);
}/* ── 1. Marquee (T1) ──────────────────────────────────────────────────────────────────── */
@layer components {
.fx-marquee {
--fx-gap: 3rem;
display: flex;
gap: var(--fx-gap);
overflow: hidden;
padding-block: 0.25rem; /* room for focus rings */
mask-image: linear-gradient(to right, transparent, black 8%, black 92%, transparent);
}
.fx-marquee__track {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: space-around;
gap: var(--fx-gap);
min-width: 100%;
margin: 0;
padding: 0;
list-style: none;
animation: fx-marquee var(--fx-marquee-duration, 40s) linear infinite;
}
.fx-marquee[data-reverse] .fx-marquee__track { animation-direction: reverse; }
.fx-marquee:focus-within .fx-marquee__track,
.fx-marquee-wrap:has(.fx-marquee__pause:checked) .fx-marquee__track { animation-play-state: paused; }
@media (hover: hover) and (pointer: fine) {
.fx-marquee:hover .fx-marquee__track { animation-play-state: paused; }
}
@media (prefers-reduced-motion: reduce) {
.fx-marquee { overflow-x: auto; mask-image: none; }
.fx-marquee__track { animation: none; }
.fx-marquee__track[aria-hidden] { display: none; }
.fx-marquee-wrap label:has(.fx-marquee__pause) { display: none; }
}
}
@keyframes fx-marquee { to { transform: translateX(calc(-100% - var(--fx-gap))); } }<Marquee label="Companies using Acme">
{logos.map((l) => (
<li key={l.name}>
<Image src={l.src} alt={l.name} width={120} height={32} className="h-8 w-auto opacity-80" />
</li>
))}
</Marquee>Each track has min-width: 100%, so the loop is seamless with any item count. For fewer than about 6 logos, use a static row instead.
2. SpotlightCard (pointer glow on a feature grid)
- Inspired by: React Bits
SpotlightCard(https://reactbits.dev), Magic UImagic-card. - Upstream bugs fixed:
setStateon every mousemove, which re-renders children and repaints a full-cardradial-gradient; a conditionaluseMotionTemplatehook (magic-card); hardcoded neutral palette. - Technique: a pre-rasterised radial blob moved through the
translateproperty. Pointer events are coalesced into one rAF, with onegetBoundingClientRectper frame and zero React renders. A 120mstranslatetransition retargets every frame, which reads as a light spring without motion. - Tier: T2, ~0.5 KB.
- Reduced motion / touch / pen: the effect never attaches, leaving a static
surface-card. - A11y: the glow is
aria-hidden. Keyboard parity comes from:focus-within, which raises the border to--brand-line. Put a real link or button inside the card. - SSR: a client component, but children render on the server (pass Server Components as
children).
"use client";
import { useEffect, useRef, type ComponentProps } from "react";
import { cn } from "@/lib/cn";
/** Pointer-following glow. Zero React renders per move: one rAF-coalesced `translate` write on a
* pre-rasterised blob. Touch, pen, keyboard and reduced motion get the static card (+ focus border). */
export function SpotlightCard({ className, children, ...rest }: ComponentProps<"div">) {
const cardRef = useRef<HTMLDivElement>(null);
const glowRef = useRef<HTMLSpanElement>(null);
useEffect(() => {
const card = cardRef.current;
const glow = glowRef.current;
if (!card || !glow) return;
if (!matchMedia("(hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference)").matches) return;
let raf = 0;
let px = 0;
let py = 0;
const write = () => {
raf = 0;
const r = card.getBoundingClientRect(); // one read per frame, not per event
glow.style.translate = `${px - r.left}px ${py - r.top}px`;
};
const onMove = (e: PointerEvent) => {
if (e.pointerType !== "mouse") return;
px = e.clientX;
py = e.clientY;
if (!raf) raf = requestAnimationFrame(write);
};
card.addEventListener("pointermove", onMove, { passive: true });
return () => {
card.removeEventListener("pointermove", onMove);
if (raf) cancelAnimationFrame(raf);
};
}, []);
return (
<div ref={cardRef} className={cn("fx-spot surface-card p-6", className)} {...rest}>
<span ref={glowRef} className="fx-spot__glow" aria-hidden="true" />
{children}
</div>
);
}/* ── 2. Spotlight card (T2, ~0.5 KB) ──────────────────────────────────────────────────── */
@layer components {
.fx-spot {
position: relative;
isolation: isolate;
overflow: hidden;
}
.fx-spot__glow {
position: absolute;
top: 0;
left: 0;
z-index: -1;
width: var(--fx-spot-size, 22rem);
aspect-ratio: 1;
border-radius: 50%;
pointer-events: none;
background: radial-gradient(closest-side, color-mix(in oklab, var(--brand) 22%, transparent), transparent);
transform: translate(-50%, -50%); /* centres on the pointer; JS writes the `translate` property */
opacity: 0;
transition: opacity var(--duration-slow) ease, translate 120ms var(--ease-out);
}
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
.fx-spot:hover .fx-spot__glow { opacity: 1; }
}
.fx-spot:focus-within { border-color: var(--brand-line); } /* keyboard parity */
}Use one spotlight grid per page (it is the "secondary" effect). Each card owns its own listener, so there are no window listeners.
3. RevealText (per-word headline reveal)
- Inspired by: React Bits
SplitText/BlurText(https://reactbits.dev), Aceternitytext-generate-effect, Magic UItext-animate. - Upstream bugs fixed: GSAP + SplitText (45–70 KB); SSR text at
opacity:0(invisible without JS, LCP delayed to the end of the stagger); hydration flash; per-character nodes; duplicatearia-label+sr-onlycopies. - Tier: T1, 0 KB JS.
trigger="load"(hero h1): words rise 0.3em via@starting-stylewith a 40ms stagger capped at 400ms. Opacity stays 1, so the LCP is the first paint. This also works on client navigations, since new elements get a starting style too.trigger="scroll"(below the fold only): each word fades and rises, scrubbed byanimation-timeline: view(). Browsers without scroll timelines show static text.- Reduced motion: static text, and
--duration-sloweris 0 in base.css. - A11y: one copy of the text with real spaces between inline-block words, so screen readers read it normally.
- SSR: full text in the server HTML (verified with JS disabled).
import type { CSSProperties, ElementType } from "react";
import { cn } from "@/lib/cn";
type RevealTextProps = {
text: string;
as?: ElementType;
/** "load": hero/LCP-safe rise on first paint (opacity never 0).
* "scroll": scrubbed by a CSS view() timeline where supported, static elsewhere. Below the fold only. */
trigger?: "load" | "scroll";
className?: string;
};
/** Server Component. Splits per word (never per character); real spaces keep one readable copy for AT. */
export function RevealText({ text, as: Tag = "h2", trigger = "scroll", className }: RevealTextProps) {
const words = text.trim().split(/\s+/);
return (
<Tag className={cn("fx-reveal", className)} data-trigger={trigger}>
{words.map((word, i) => (
<span key={i}>
<span className="fx-reveal__word" style={{ "--i": i } as CSSProperties}>
{word}
</span>
{i < words.length - 1 ? " " : null}
</span>
))}
</Tag>
);
}/* ── 3. Text reveal (T1) ──────────────────────────────────────────────────────────────── */
@layer components {
.fx-reveal { --fx-stagger: 40ms; }
.fx-reveal__word { display: inline-block; }
@media (prefers-reduced-motion: no-preference) {
/* load: movement only, opacity stays 1 → LCP is the first paint */
.fx-reveal[data-trigger="load"] .fx-reveal__word {
transition: translate var(--duration-slower) var(--ease-out-expo);
transition-delay: min(calc(var(--i) * var(--fx-stagger)), 400ms);
@starting-style { translate: 0 0.3em; }
}
/* scroll: scrubbed by the view timeline; unsupported browsers show static text */
@supports (animation-timeline: view()) {
.fx-reveal[data-trigger="scroll"] .fx-reveal__word {
animation: fx-rise linear both;
animation-timeline: view();
animation-range: entry calc(5% + var(--i) * 1.5%) cover calc(25% + var(--i) * 1.5%);
}
}
}
}
@keyframes fx-rise { from { opacity: 0; translate: 0 0.5em; } to { opacity: 1; translate: 0 0; } }<RevealText as="h1" trigger="load" text="Ship interfaces that feel inevitable" className="text-display-lg" />Keep it to 1–2 headings per page. For block-level section reveals use base.css reveal; for masked line rises use motion → references/recipes.md §10. Don't add clip-path or opacity:0 to the load variant: whether a fully clipped first frame counts as painted for LCP is unverified.
4. BeamBorder (a light travelling around one card)
- Inspired by: Magic UI
border-beam(https://magicui.design/docs/components/border-beam), React BitsStarBorder, motion-primitivesborder-trail. - Upstream bugs fixed: motion animating
offset-distanceon the JS main thread forever (border-beam); keyframes that exist only in a commentedtailwind.config.js, so the effect is dead on v4 (StarBorder);withouttype; hardcoded#ffaa40 → #9c40ff. - Technique: an oversized conic-gradient layer rotates (compositor
transform) behind a 1px padding gap. The inner surface must be opaque. - Tier: T1, 0 KB.
- Reduced motion: a static
--brand-lineborder. - A11y: purely decorative, with no extra DOM.
- SSR: a Server Component.
import type { ComponentProps } from "react";
import { cn } from "@/lib/cn";
type Props = ComponentProps<"div"> & {
/** Set when height > ~1.7 × width so the rotating layer still covers the corners. */
tall?: boolean;
innerClassName?: string;
};
/** Server Component. A 1px border whose brand highlight travels around once per 6s.
* Compositor-only (rotates a conic layer); paused offscreen via data-fx; static brand border under reduced motion. */
export function BeamBorder({ tall, className, innerClassName, children, ...rest }: Props) {
return (
<div data-fx="beam" data-tall={tall || undefined} className={cn("fx-beam", className)} {...rest}>
<div className={cn("fx-beam__inner p-6", innerClassName)}>{children}</div>
</div>
);
}/* ── 4. Border beam (T1) ──────────────────────────────────────────────────────────────── */
@layer components {
.fx-beam {
position: relative;
isolation: isolate;
overflow: hidden;
padding: 1px; /* border width */
border-radius: var(--radius-xl);
background: var(--line);
}
.fx-beam::before {
content: "";
position: absolute;
z-index: -1;
top: 50%;
left: 50%;
width: 200%; /* square ≥ diagonal while height ≤ 1.7 × width; use data-tall otherwise */
aspect-ratio: 1;
background: conic-gradient(from 0turn, transparent 0 75%, color-mix(in oklab, var(--brand) 90%, transparent) 90%, transparent 100%);
transform: translate(-50%, -50%) rotate(0turn);
animation: fx-spin var(--fx-beam-duration, 6s) linear infinite;
}
.fx-beam[data-tall]::before { width: auto; height: 200%; }
.fx-beam__inner {
height: 100%;
border-radius: calc(var(--radius-xl) - 1px);
background: var(--surface); /* must be opaque */
}
@media (prefers-reduced-motion: reduce) {
.fx-beam { background: var(--brand-line); }
.fx-beam::before { animation: none; display: none; }
}
}
@keyframes fx-spin { to { transform: translate(-50%, -50%) rotate(1turn); } }Use it on the one card that represents a state ("Recommended", "Live", "Provisioning"): ≤1 in view, ≤3 per page. Pass tall when height is more than 1.7× the width. For a transparent interior, animate an @property --angle conic border-image instead; that repaints every frame, so use one per viewport at most.
5. PatternBackdrop (grid, dots, glow) and grain
- Inspired by: Magic UI
grid-pattern/dot-pattern(https://magicui.design), Aceternitybackground-beams/dotted-glow-background, pattern-craft. - Upstream bugs fixed:
dot-patternwith glow renders ~5,000s, each with an infinite spring;background-beamsanimates 50 SVG gradients and callsMath.random()in render (hydration mismatch);dotted-glow-backgroundkeeps its rAF running offscreen and callsgetBoundingClientRectinside the loop. - Tier: T0, 0 KB, no animation.
- Rules: one glow per page, and patterns are always masked (an unmasked full-bleed grid is a template tell). For grain, add base.css
grainto the section. It reads the direction's--grain-opacity(0 in light, ≤0.035 in dark), so never hardcode an opacity. - A11y / SSR:
aria-hidden, Server Component.
import { cn } from "@/lib/cn";
type Props = { pattern?: "grid" | "dots" | "none"; glow?: boolean; className?: string };
/** Server Component, zero JS, no animation. One masked pattern + one brand glow.
* Parent needs `relative isolate`. Grain: add the base.css `grain` utility to the section instead. */
export function PatternBackdrop({ pattern = "grid", glow = true, className }: Props) {
return (
<div aria-hidden="true" className={cn("pointer-events-none absolute inset-0 -z-10", glow && "bg-fx-glow", className)}>
{pattern !== "none" ? <div className={cn("absolute inset-0", pattern === "grid" ? "bg-fx-grid" : "bg-fx-dots")} /> : null}
</div>
);
}/* ── 5. Backgrounds (T0, zero JS). Grain: use base.css `grain` (per-direction --grain-opacity). ── */
@utility bg-fx-grid {
--fx-cell: 32px;
--fx-line: color-mix(in oklab, var(--fg) 7%, transparent);
background-image:
linear-gradient(to right, var(--fx-line) 1px, transparent 1px),
linear-gradient(to bottom, var(--fx-line) 1px, transparent 1px);
background-size: var(--fx-cell) var(--fx-cell);
background-position: center top;
mask-image: radial-gradient(ellipse 70% 60% at 50% 0%, black 30%, transparent 75%);
}
@utility bg-fx-dots {
--fx-cell: 20px;
--fx-dot: color-mix(in oklab, var(--fg) 14%, transparent);
background-image: radial-gradient(circle at center, var(--fx-dot) 1px, transparent 1.5px);
background-size: var(--fx-cell) var(--fx-cell);
mask-image: radial-gradient(ellipse 60% 50% at 50% 40%, black 20%, transparent 70%);
}
@utility bg-fx-glow {
background-image: radial-gradient(60% 50% at 50% -10%, color-mix(in oklab, var(--brand) 14%, transparent), transparent 70%);
}<section className="relative isolate grain">
<PatternBackdrop pattern="grid" />
…hero content…
</section>Tune with arbitrary properties rather than new CSS: [--fx-cell:24px], and for density [--fx-line:color-mix(in_oklab,var(--fg)_10%,transparent)].
6. NumberTicker (stats that count up)
- Inspired by: Magic UI
number-ticker(https://magicui.design/docs/components/number-ticker), React BitsCountUp. - Upstream bugs fixed: server HTML contains
0(Magic UI) or an empty string (React Bits), so crawlers, link previews and no-JS users see no number; anew Intl.NumberFormatevery frame; hardcodeden-US; spring damping 60 (slow settle); CLS as the width grows; replays on every scroll-by. - Prefer
@number-flow/reactwhen the dependency is acceptable (it handles locale, digit width and reduced motion). Use this component when it isn't. - Behaviour: SSR renders the final formatted value. If the number is already visible on arrival, it never animates. If it starts offscreen, it is reset to 0 while invisible, reserves its final pixel width, and counts up once (900ms, ease-out quart) when 60% visible.
- Tier: T2, ~0.6 KB.
- Reduced motion: the final value, no animation.
- A11y: the animated copy is
aria-hidden; screen readers get the stablesr-onlycopy.data-numericgives tabular figures. - SSR:
12,400in the HTML (verified with JS disabled).
"use client";
import { useEffect, useMemo, useRef } from "react";
const formatters = new Map<string, Intl.NumberFormat>();
function getFormatter(locale: string, optionsKey: string) {
const key = `${locale}|${optionsKey}`;
let f = formatters.get(key);
if (!f) formatters.set(key, (f = new Intl.NumberFormat(locale, JSON.parse(optionsKey))));
return f;
}
type Props = {
value: number;
locale?: string;
options?: Intl.NumberFormatOptions;
durationMs?: number;
className?: string;
};
/** Server HTML contains the final formatted value. Counts up once, only if it starts offscreen.
* Screen readers get the stable copy; the animated copy is aria-hidden with reserved width (no CLS). */
export function NumberTicker({ value, locale = "en-US", options, durationMs = 900, className }: Props) {
const ref = useRef<HTMLSpanElement>(null);
const optionsKey = JSON.stringify(options ?? {});
const fmt = useMemo(() => getFormatter(locale, optionsKey), [locale, optionsKey]);
const final = fmt.format(value);
useEffect(() => {
const el = ref.current;
if (!el || matchMedia("(prefers-reduced-motion: reduce)").matches) return;
let raf = 0;
let first = true;
const io = new IntersectionObserver(
([entry]) => {
if (first) {
first = false;
if (entry.isIntersecting) return io.disconnect(); // visible on arrival: never yank to 0
el.style.minWidth = `${el.offsetWidth}px`; // reserve the final width: no shift while counting
el.textContent = fmt.format(0); // offscreen, invisible reset
return;
}
if (!entry.isIntersecting) return;
io.disconnect(); // once
let start = 0;
const tick = (t: number) => {
if (!start) start = t;
const p = Math.min((t - start) / durationMs, 1);
el.textContent = fmt.format(value * (1 - (1 - p) ** 4)); // ease-out quart
if (p < 1) raf = requestAnimationFrame(tick);
};
raf = requestAnimationFrame(tick);
},
{ threshold: 0.6 },
);
io.observe(el);
return () => {
io.disconnect();
if (raf) cancelAnimationFrame(raf);
el.textContent = final;
el.style.minWidth = "";
};
}, [value, durationMs, fmt, final]);
return (
<span className={className} data-numeric>
<span ref={ref} aria-hidden="true" className="inline-block tabular-nums">
{final}
</span>
<span className="sr-only">{final}</span>
</span>
);
}<p className="font-display text-display-sm"><NumberTicker value={12400} /></p>
<NumberTicker value={98.6} options={{ maximumFractionDigits: 1 }} />%7. Magnetic (a CTA that leans toward the cursor)
- Inspired by: React Bits
Magnet(https://reactbits.dev), motion-primitivesmagnetic. - Upstream bugs fixed: one
windowmousemove listener per instance;getBoundingClientRect+setStateon every move; no touch or reduced-motion gate;ease-in-outrelease. - Technique: element-scoped pointer events. It measures once on enter and writes
translateonce per frame. A CSS transition does the spring-back (450ms--ease-out) and smooth tracking (120ms). Strength is capped at 0.3, so the element stays under the cursor andpointerleavestays reliable. A::before16px halo widens the capture zone without layout. - Tier: T2, ~0.5 KB.
- Reduced motion / touch: never attaches.
- A11y: wraps a real
/. Press feedback stays on the child (templateButtonhaspress). Never use it on toolbars, nav or anything clicked daily. - SSR: a client wrapper; children render on the server.
"use client";
import { useEffect, useRef, type ReactNode } from "react";
/** Pulls its child toward the pointer. Element-scoped listeners (no window listener), measure once on
* enter, one `translate` write per frame, CSS transition springs it home. Mouse + motion-OK only. */
export function Magnetic({ children, strength = 0.25 }: { children: ReactNode; strength?: number }) {
const ref = useRef<HTMLSpanElement>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
if (!matchMedia("(hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference)").matches) return;
const k = Math.min(strength, 0.3); // stay under the cursor so pointerleave stays reliable
let raf = 0;
let rect: DOMRect | null = null;
let x = 0;
let y = 0;
const onEnter = () => {
el.style.translate = "";
rect = el.getBoundingClientRect();
el.dataset.magnet = "on";
};
const onMove = (e: PointerEvent) => {
if (!rect || e.pointerType !== "mouse") return;
x = (e.clientX - (rect.left + rect.width / 2)) * k;
y = (e.clientY - (rect.top + rect.height / 2)) * k;
if (!raf)
raf = requestAnimationFrame(() => {
raf = 0;
el.style.translate = `${x}px ${y}px`;
});
};
const onLeave = () => {
if (raf) cancelAnimationFrame(raf);
raf = 0;
rect = null;
delete el.dataset.magnet;
el.style.translate = "";
};
el.addEventListener("pointerenter", onEnter);
el.addEventListener("pointermove", onMove, { passive: true });
el.addEventListener("pointerleave", onLeave);
return () => {
el.removeEventListener("pointerenter", onEnter);
el.removeEventListener("pointermove", onMove);
el.removeEventListener("pointerleave", onLeave);
if (raf) cancelAnimationFrame(raf);
};
}, [strength]);
return (
<span ref={ref} className="fx-magnet">
{children}
</span>
);
}/* ── 7. Magnetic (T2, ~0.5 KB) ────────────────────────────────────────────────────────── */
@layer components {
.fx-magnet {
position: relative;
display: inline-block;
transition: translate 450ms var(--ease-out); /* release: settles softly */
}
.fx-magnet[data-magnet] { transition-duration: 120ms; } /* tracking: retargets each frame */
.fx-magnet::before { content: ""; position: absolute; inset: -16px; } /* capture zone, no layout */
}<Magnetic><Button size="lg" href="/start">Get started</Button></Magnetic>8. ShinyText (a sweep of light across a label)
- Inspired by: React Bits
ShinyText(https://reactbits.dev), Magic UIanimated-shiny-text, motion-primitivestext-shimmer. - Upstream bugs fixed: a perpetual
useAnimationFrameJS loop per instance writingbackground-position(it keeps ticking even whendisabled); no offscreen or reduced-motion handling. - Tier: T1-paint, 0 KB.
background-clip: textcan't be moved by transform, so this is a paint animation. It gets its own layer (will-change: transform) so each frame repaints only the label. - Measured: without its own layer, a shiny eyebrow on top of
AuroraBackdropdropped 14–46% of frames while scrolling (headless, software raster); without the shine, 0–4%. Don't put ShinyText over an animated backdrop even with the fix. They count as two effects in one viewport anyway. - Reduced motion: plain
--fg-mutedtext. - A11y: real text, and contrast stays within the fg scale. Never on h1/h2 (that is
design/gradient-text), never on body copy, never in brand colour. - SSR: a Server Component.
import type { ComponentProps } from "react";
import { cn } from "@/lib/cn";
/** Server Component. A light sweep across one short muted string (eyebrow, badge). Paint animation:
* one per viewport, never on h1/body copy, never on top of another animated layer. */
export function ShinyText({ className, ...rest }: ComponentProps<"span">) {
return <span data-fx="shine" className={cn("fx-shine", className)} {...rest} />;
}/* ── 8. Shiny text (T1-paint: one short string per viewport) ─────────────────────────── */
@layer components {
.fx-shine {
color: var(--fg-muted); /* fallback */
background-image: linear-gradient(100deg, var(--fg-muted) 40%, var(--fg) 50%, var(--fg-muted) 60%);
background-size: 250% 100%;
background-position: 100% 0;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
animation: fx-shine 3.2s var(--ease-in-out) infinite;
/* own layer: otherwise each frame's repaint invalidates whatever big layer it shares (e.g. text over
an animated backdrop repaints the whole hero: measured 15% → 1.4% dropped frames) */
will-change: transform;
}
@media (prefers-reduced-motion: reduce) {
.fx-shine { animation: none; background: none; -webkit-text-fill-color: currentColor; will-change: auto; }
}
}
@keyframes fx-shine { 0% { background-position: 100% 0; } 55%, 100% { background-position: 0% 0; } }<p className="label-mono"><ShinyText>Now with agent skills</ShinyText></p>9. AuroraBackdrop (WebGL aurora → CSS)
- Inspired by: React Bits
Aurora/Silk/SoftAurora(https://reactbits.dev), Aceternityaurora-background. - Upstream bugs fixed: three + R3F (~150 KB) or ogl for one full-screen quad;
new Color()×3 per frame; effect deps that rebuild the GL context; no DPR cap, offscreen pause or reduced motion. - Technique: three large radial gradients with the softness baked in (no
filter: blur), each drifting 27–41s viatransform. That is 3 composited layers and zero repaints while idle. It gives about 80% of the shader mood at 0 KB. Single hue by construction (--brand): multi-hue meshes are a banned trope (design/purple-gradient). - Measured fix: fading toward the fold with
mask-imageon the container forced a re-rendered surface every frame (10% dropped frames). A static gradient overlay (::afterto--bg) measured 0%. If the section background isn't--bg, set--fx-aurora-fade. - Tier: T1, 0 KB. This is the page's signature, so nothing else animates in the hero.
- Reduced motion: the blobs hold still (static glow).
- A11y / SSR:
aria-hidden, Server Component.
import type { CSSProperties } from "react";
/** Server Component. Three pre-softened brand blobs drifting on the compositor (no filter: blur, no WebGL).
* Parent needs `relative isolate`. Masked toward the fold so it never sits behind body copy. */
export function AuroraBackdrop({ intensity = 0.2 }: { intensity?: number }) {
return (
<div data-fx="aurora" aria-hidden="true" className="fx-aurora" style={{ "--fx-aurora-opacity": intensity } as CSSProperties}>
<span />
<span />
<span />
</div>
);
}/* ── 9. CSS aurora (T1, replaces WebGL Aurora/Silk) ──────────────────────────────────── */
@layer components {
.fx-aurora {
position: absolute;
inset: 0;
z-index: -1;
overflow: hidden;
pointer-events: none;
contain: strict;
}
/* fade toward the fold so it never sits behind body copy. A static overlay, not mask-image:
a mask over animated children forces a re-rendered surface every frame (measured 10% dropped frames). */
.fx-aurora::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(to bottom, transparent 35%, var(--fx-aurora-fade, var(--bg)) 92%);
}
.fx-aurora > span {
position: absolute;
width: 55vmax;
aspect-ratio: 1;
border-radius: 50%;
opacity: var(--fx-aurora-opacity, 0.2);
animation: fx-drift 32s var(--ease-in-out) infinite alternate;
}
.fx-aurora > span:nth-child(1) {
top: -30vmax;
left: -12vmax;
background: radial-gradient(closest-side, var(--brand), color-mix(in oklab, var(--brand) 35%, transparent) 45%, transparent);
}
.fx-aurora > span:nth-child(2) {
top: -26vmax;
right: -20vmax;
background: radial-gradient(closest-side, color-mix(in oklab, var(--brand) 55%, var(--fg)), transparent);
animation-duration: 41s;
animation-direction: alternate-reverse;
}
.fx-aurora > span:nth-child(3) {
top: -10vmax;
left: 30%;
width: 40vmax;
background: radial-gradient(closest-side, color-mix(in oklab, var(--brand) 45%, transparent), transparent);
animation-duration: 27s;
opacity: calc(var(--fx-aurora-opacity, 0.2) * 0.65);
}
@media (prefers-reduced-motion: reduce) {
.fx-aurora > span { animation: none; }
}
}
@keyframes fx-drift {
from { transform: translate3d(0, 0, 0) scale(1); }
to { transform: translate3d(8vmax, 5vmax, 0) scale(1.12); }
}<section className="relative isolate overflow-hidden">
<AuroraBackdrop intensity={0.2} />
…hero…
</section>Keep intensity ≤ 0.25 on dark directions, and check gutters stay near the canvas colour. If the brief truly needs a shader, follow speed → references/rendering-smoothness.md §8 (WebGL2 hook) and motion → references/webgl-canvas.md, with this component as the CSS fallback underneath.
10. HeroSpotlight (angled light behind the hero)
- Inspired by: Aceternity
spotlight(https://ui.aceternity.com/components/spotlight). - Upstream bugs fixed:
animate-spotlightships with no CSS in the registry item, so the SVG stays atopacity-0forever on Tailwind v4 (silently dead); hardcodedid="filter"collides across instances; a 151pxfeGaussianBlur;z-[1]sits over content.spotlight-newloops forever. - Technique: one rotated elliptical radial gradient that fades in once (1.4s
--ease-out-expo, 0.2s delay).light-dark()tints it with--brandin light schemes and--fgin dark ones. - Tier: T1 one-shot, 0 KB. Starting at opacity 0 is safe because gradients are never LCP candidates.
- Reduced motion: shown immediately.
- A11y / SSR:
aria-hidden, Server Component.
import { cn } from "@/lib/cn";
/** Server Component, zero JS. A soft angled light (brand-tinted in light mode, fg-tinted in dark) that
* fades in once on load. No SVG filter, no ids, no infinite loop. Parent needs `relative isolate`. */
export function HeroSpotlight({ className }: { className?: string }) {
return (
<div aria-hidden="true" className={cn("pointer-events-none absolute inset-0 -z-10 overflow-hidden", className)}>
<div className="fx-spotlight" />
</div>
);
}/* ── 10. Hero spotlight (T1 one-shot) ─────────────────────────────────────────────────── */
@layer components {
.fx-spotlight {
position: absolute;
top: -45%;
left: -25%;
width: 110%;
height: 130%;
background: radial-gradient(
ellipse 38% 14% at 50% 50%,
light-dark(color-mix(in oklab, var(--brand) 16%, transparent), color-mix(in oklab, var(--fg) 16%, transparent)),
transparent 70%
);
transform: rotate(32deg);
animation: fx-spotlight-in 1.4s var(--ease-out-expo) 0.2s both; /* decorative, never LCP */
}
@media (prefers-reduced-motion: reduce) {
.fx-spotlight { animation: none; }
}
}
@keyframes fx-spotlight-in {
from { opacity: 0; transform: translate(-6%, -5%) rotate(32deg) scale(0.96); }
to { opacity: 1; transform: translate(0, 0) rotate(32deg) scale(1); }
}Verification log (2026-09-16)
next buildpassed (TypeScript strict); every demo route is static (○).void lintreported 0 errors, and no findings insrc/components/fxorfx.css.- Server HTML with JavaScript disabled: the h1 text is complete, the ticker shows
12,400, the marquee duplicate hasaria-hidden="true" inert, and no inlineopacity:0. - Playwright (Chromium 149): SpotlightCard glow
translate: 120px 60px, opacity 1 on hover; Magnetictranslate: 14.8px 5.5pxwhile tracking; the below-fold ticker counts 0 → 65.8 → 98.6 once, with thesr-onlycopy stable at 98.6; FxGate setdata-fx-pausedon the offscreen shine only; no console errors from fx code. - Screenshots at 1440px (dark, light,
--force-prefers-reduced-motion) and 390px: all effects visible and on-token. Reduced motion showed the static marquee (no mask), a static brand beam border, and still blobs. void smooth(desktop, scroll, headless software raster): each effect alone dropped 0–5% of frames. The full ten-effect page dropped 22–39% before the two fixes above (the shine's own layer and the aurora overlay instead of a mask) and 0% in two runs after them. The machine load average was 10–22, so repeat measurements varied by ±15 points; re-measure on your own route.void a11y: a marquee logo contrast failure withtext-fg-subtlein the light theme, measured before--fg-subtlewas raised to AA (fixed by usingtext-fg-muted, noted in §1).