Section
Philosophy Section
The anti-jank promise: a counter that runs to 60fps and a smooth curve that draws itself beside a crossed-out jagged one.
The Anti-Jank Promise
0fps
Not a tagline — a promise, held on every scroll trigger and every hover state. If an animation drops a frame on a mid-range phone, it doesn't ship.
Dropped Frames
Kinematic Studio
Zero Dropped FramesSub-Second LoadsEvery Interaction Profiled
The counter runs to 60 and the smooth line draws in the first time it comes into view
import PhilosophySection from "@/components/ui/philosophy-section";
export default function PhilosophySectionDemo({ fullscreen }: { fullscreen?: boolean }) {
if (fullscreen) {
return (
<div className="w-full bg-kine-bg">
<p className="grid h-[70dvh] place-items-center font-kine-body text-kine-accent/50">Scroll down</p>
<PhilosophySection />
</div>
);
}
return (
<div className="w-full min-w-0 space-y-3">
<div className="w-full overflow-hidden rounded-card border border-line">
<PhilosophySection />
</div>
<p className="text-center text-xs text-faint">
The counter runs to 60 and the smooth line draws in the first time it comes into view
</p>
</div>
);
}Installation
1. Install the dependencies.
terminal npm install gsap2. Add the
cnhelper, if you don't already have it.lib/utils.ts import { clsx, type ClassValue } from "clsx"; import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); }3. Copy the component into your project.
components/ui/philosophy-section.tsx 'use client' import { useEffect, useRef } from 'react' import gsap from 'gsap' const PHILOSOPHY_BADGES = ['Zero Dropped Frames', 'Sub-Second Loads', 'Every Interaction Profiled'] // Philosophy callout — anti-jank / 60fps as a designed statement. export default function PhilosophySection() { const sectionRef = useRef<HTMLDivElement>(null) const counterRef = useRef<HTMLSpanElement>(null) const smoothPathRef = useRef<SVGPathElement>(null) const hasPlayedRef = useRef(false) useEffect(() => { const section = sectionRef.current const counter = counterRef.current const smoothPath = smoothPathRef.current if (!section || !counter || !smoothPath) return const total = smoothPath.getTotalLength() smoothPath.style.strokeDasharray = `${total}` smoothPath.style.strokeDashoffset = `${total}` const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting && !hasPlayedRef.current) { hasPlayedRef.current = true const proxy = { value: 0 } gsap.to(proxy, { value: 60, duration: 1.4, ease: 'power2.out', onUpdate: () => { counter.textContent = String(Math.round(proxy.value)) }, }) gsap.to(smoothPath, { strokeDashoffset: 0, duration: 1.4, ease: 'power2.out' }) } }, { threshold: 0.4 } ) observer.observe(section) return () => observer.disconnect() }, []) return ( <section ref={sectionRef} className="border-t border-white/10 bg-kine-bg px-6 py-24 md:px-12 md:py-40"> <div className="mx-auto flex max-w-4xl flex-col items-center gap-10 text-center"> <span className="font-mono text-xs font-bold uppercase tracking-widest text-kine-primary"> The Anti-Jank Promise </span> <div className="flex items-baseline gap-2"> <span ref={counterRef} className="font-kine-display text-8xl leading-none text-kine-primary md:text-[11rem]" > 0 </span> <span className="font-kine-display text-4xl uppercase leading-none text-kine-accent md:text-6xl">fps</span> </div> <p className="max-w-2xl font-kine-body text-lg leading-relaxed text-kine-accent/70 md:text-xl"> Not a tagline — a promise, held on every scroll trigger and every hover state. If an animation drops a frame on a mid-range phone, it doesn't ship. </p> {/* Smooth curve vs. a crossed-out jagged line — the promise, drawn. */} <div className="flex w-full max-w-md flex-col gap-6"> <div className="flex items-center gap-4 opacity-40"> <svg viewBox="0 0 200 40" className="h-8 flex-1" fill="none"> <path d="M4 20 L30 6 L50 34 L70 6 L90 34 L110 6 L130 34 L150 6 L170 34 L196 20" stroke="#e2e8f0" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" /> <line x1="0" y1="0" x2="200" y2="40" stroke="#e2e8f0" strokeWidth={1.5} /> </svg> <span className="font-mono text-xs uppercase tracking-widest text-kine-accent/50">Dropped Frames</span> </div> <div className="flex items-center gap-4"> <svg viewBox="0 0 200 40" className="h-8 flex-1" fill="none"> <path ref={smoothPathRef} d="M4 30 C 40 30, 40 10, 76 10 S 112 30, 148 30 S 176 14, 196 14" stroke="#ceff00" strokeWidth={3} strokeLinecap="round" /> </svg> <span className="font-mono text-xs uppercase tracking-widest text-kine-primary">Kinematic Studio</span> </div> </div> <div className="mt-4 flex flex-wrap items-center justify-center gap-3"> {PHILOSOPHY_BADGES.map((badge) => ( <span key={badge} className="rounded-full border border-kine-primary/30 bg-kine-primary/5 px-5 py-2 font-mono text-xs font-bold uppercase tracking-widest text-kine-primary" > {badge} </span> ))} </div> </div> </section> ) }styles/kinematics.css /* Kinematic Studio's palette and type, for the components ported from its site. Namespaced as kine-* so they sit beside this library's own tokens — Kinematics calls its off-white "accent", which here is already the blue. Import this from the stylesheet that imports Tailwind, so the @theme block turns into utilities: bg-kine-bg, text-kine-primary/70, font-kine-display and so on. */ @theme { --color-kine-bg: #0f1113; --color-kine-primary: #ceff00; --color-kine-surface: #2b2e34; --color-kine-accent: #e2e8f0; /* Audiowide for headlines, Geo for body copy. Geo breaks up at small sizes, so tiny labels use the plain sans instead. */ --font-kine-display: "Audiowide", ui-sans-serif, system-ui, sans-serif; --font-kine-body: "Geo", ui-sans-serif, system-ui, sans-serif; /* Morni, the restaurant client in the Emoji Cursor demo. */ --font-morni-brand: "Modak", ui-sans-serif, system-ui, sans-serif; --font-morni-body: "Poppins", ui-sans-serif, system-ui, sans-serif; } /* Manifesto emoji markers — the box grows from square to rectangle as scroll reaches it; once mostly grown, the emoji starts rolling, paused until then via animation-play-state set inline. */ @keyframes manifesto-emoji-roll { to { transform: rotate(360deg); } } /* Expertise cards — small, always-looping visuals tying each card's animation to its own subject (motion, layout, load speed, ranking, language). CSS keyframes rather than GSAP, since they run for as long as the card is mounted. */ @keyframes expertise-dash { to { stroke-dashoffset: -24; } } .expertise-flow-line { stroke-dasharray: 6 6; animation: expertise-dash 1s linear infinite; } @keyframes expertise-block-a { 0%, 100% { width: 85%; } 50% { width: 45%; } } @keyframes expertise-block-b { 0%, 100% { width: 55%; } 50% { width: 90%; } } @keyframes expertise-block-c { 0%, 100% { width: 70%; } 50% { width: 30%; } } .expertise-block-a { animation: expertise-block-a 3.2s ease-in-out infinite; } .expertise-block-b { animation: expertise-block-b 3.2s ease-in-out infinite 0.3s; } .expertise-block-c { animation: expertise-block-c 3.2s ease-in-out infinite 0.6s; } @keyframes expertise-fill { 0% { width: 6%; opacity: 0.5; } 70% { width: 100%; opacity: 1; } 85% { width: 100%; opacity: 1; } 100% { width: 6%; opacity: 0.5; } } .expertise-fill-bar { animation: expertise-fill 2.2s cubic-bezier(0.65, 0, 0.35, 1) infinite; } @keyframes expertise-rank-bar-1 { 0%, 100% { height: 30%; } 50% { height: 80%; } } @keyframes expertise-rank-bar-2 { 0%, 100% { height: 55%; } 50% { height: 25%; } } @keyframes expertise-rank-bar-3 { 0%, 100% { height: 40%; } 50% { height: 95%; } } @keyframes expertise-rank-bar-4 { 0%, 100% { height: 70%; } 50% { height: 50%; } } .expertise-rank-1 { animation: expertise-rank-bar-1 2.4s ease-in-out infinite; } .expertise-rank-2 { animation: expertise-rank-bar-2 2.4s ease-in-out infinite 0.15s; } .expertise-rank-3 { animation: expertise-rank-bar-3 2.4s ease-in-out infinite 0.3s; } .expertise-rank-4 { animation: expertise-rank-bar-4 2.4s ease-in-out infinite 0.45s; } @keyframes expertise-lang-cross { 0%, 45% { opacity: 1; transform: translateY(0); } 50%, 95% { opacity: 0; transform: translateY(-6px); } 100% { opacity: 1; transform: translateY(0); } } @keyframes expertise-lang-cross-alt { 0%, 45% { opacity: 0; transform: translateY(6px); } 50%, 95% { opacity: 1; transform: translateY(0); } 100% { opacity: 0; transform: translateY(6px); } } .expertise-lang-a { animation: expertise-lang-cross 3.6s ease-in-out infinite; } .expertise-lang-b { animation: expertise-lang-cross-alt 3.6s ease-in-out infinite; }
Props
| Prop | Type | Default | Description |
|---|