Skip to content
jswnthui

Section

Capabilities Accordion

Numbered services that open one at a time, each playing its own small reveal — a clip, typed code, a drawn curve, a network — beside the description.

What We Do

Every layout starts from your brand and your content, not a page-builder section library. We design in-browser with real motion in mind from the first sketch, so what ships is exactly what was designed — no flattening, no compromise.

export const revalidate = 3600
export default async function Page() {
const data = await get()
return <Fast data={data} />
}

Server components, edge rendering, and aggressive image optimization from the first commit — not bolted on before launch. The result is a site that feels instant on Kuwait's mobile networks, not just on a fast office connection.

Every scroll interaction is built by hand and profiled for 60fps — no animation library defaults, no dropped frames on mid-range phones. Motion here has a job: guiding attention, not decorating a page that would work fine without it.

Not a canned chatbot bolted onto a footer. We integrate language models where they solve an actual problem in your product — search, personalization, content generation — reviewed for cost and latency like any other feature.

Open a row — each plays its own reveal beside the description

Installation

  1. 1. Install the dependencies.

    terminal
    npm install 
  2. 2. Add the cn helper, 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. 3. Copy the component into your project.

    components/ui/capabilities-accordion.tsx
    'use client'
    
    import { useState } from 'react'
    import {
      ClipReveal,
      CodeReveal,
      DrawReveal,
      NetworkReveal,
    } from '@/components/ui/kinematics/capability-reveals'
    
    export interface Capability {
      key: string
      number: string
      title: string
      teaser: string
      body: string
      reveal: 'clip' | 'code' | 'draw' | 'network'
    }
    
    export function CapabilityRow({ cap, open, onToggle }: { cap: Capability; open: boolean; onToggle: () => void }) {
      return (
        <div className="border-b border-white/10">
          <button
            type="button"
            onClick={onToggle}
            aria-expanded={open}
            className="group flex w-full items-center gap-6 py-8 text-left"
          >
            <span className="font-kine-display text-sm text-kine-primary/50 md:text-base">{cap.number}</span>
            <span
              className={`font-kine-display flex-1 text-2xl uppercase tracking-tight transition-colors duration-300 md:text-4xl group-hover:text-kine-primary ${
                open ? 'text-kine-primary' : 'text-kine-accent'
              }`}
            >
              {cap.title}
            </span>
            <span className="hidden font-kine-body text-sm text-kine-accent/50 md:block md:max-w-xs">{cap.teaser}</span>
            <span
              className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full border border-white/20 text-kine-primary transition-transform duration-300"
              style={{ transform: open ? 'rotate(45deg)' : 'rotate(0deg)' }}
            >
              <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
                <path d="M8 2V14M2 8H14" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
              </svg>
            </span>
          </button>
    
          <div
            className="grid overflow-hidden transition-[grid-template-rows] duration-500 ease-out"
            style={{ gridTemplateRows: open ? '1fr' : '0fr' }}
          >
            <div className="min-h-0">
              <div className="flex flex-col gap-8 pb-10 md:flex-row md:items-center">
                {cap.reveal === 'clip' && <ClipReveal open={open} />}
                {cap.reveal === 'code' && <CodeReveal open={open} />}
                {cap.reveal === 'draw' && <DrawReveal open={open} />}
                {cap.reveal === 'network' && <NetworkReveal open={open} />}
                <p className="max-w-xl font-kine-body text-base leading-relaxed text-kine-accent/70">{cap.body}</p>
              </div>
            </div>
          </div>
        </div>
      )
    }
    
    const CAPABILITIES: Capability[] = [
      {
        key: 'design',
        number: '01',
        title: 'Web Design',
        teaser: 'Bespoke, Awwwards-level layouts — never a template.',
        body: 'Every layout starts from your brand and your content, not a page-builder section library. We design in-browser with real motion in mind from the first sketch, so what ships is exactly what was designed — no flattening, no compromise.',
        reveal: 'clip',
      },
      {
        key: 'development',
        number: '02',
        title: 'Web Development',
        teaser: 'Next.js builds engineered for sub-second load times.',
        body: "Server components, edge rendering, and aggressive image optimization from the first commit — not bolted on before launch. The result is a site that feels instant on Kuwait's mobile networks, not just on a fast office connection.",
        reveal: 'code',
      },
      {
        key: 'motion',
        number: '03',
        title: 'Motion Design',
        teaser: 'GSAP and Lenis-driven scroll animation, hand-tuned.',
        body: 'Every scroll interaction is built by hand and profiled for 60fps — no animation library defaults, no dropped frames on mid-range phones. Motion here has a job: guiding attention, not decorating a page that would work fine without it.',
        reveal: 'draw',
      },
      {
        key: 'ai',
        number: '04',
        title: 'AI Integrations',
        teaser: 'Real AI features, wired into the product itself.',
        body: 'Not a canned chatbot bolted onto a footer. We integrate language models where they solve an actual problem in your product — search, personalization, content generation — reviewed for cost and latency like any other feature.',
        reveal: 'network',
      },
    ]
    
    interface CapabilitiesAccordionProps {
      heading?: string
      capabilities?: Capability[]
    }
    
    /** "What We Do": numbered rows that open one at a time, each playing a small
     * reveal for its kind of work beside the full description. */
    export default function CapabilitiesAccordion({
      heading = 'What We Do',
      capabilities = CAPABILITIES,
    }: CapabilitiesAccordionProps) {
      const [openKey, setOpenKey] = useState<string>(capabilities[0]?.key ?? '')
    
      return (
        <section className="border-t border-white/10 bg-kine-bg px-6 py-24 md:px-12 md:py-32">
          <div className="mx-auto max-w-5xl">
            <h2 className="mb-16 font-kine-display text-4xl uppercase tracking-tight text-kine-primary md:text-6xl">
              {heading}
            </h2>
    
            <div>
              {capabilities.map((cap) => (
                <CapabilityRow
                  key={cap.key}
                  cap={cap}
                  open={openKey === cap.key}
                  onToggle={() => setOpenKey(openKey === cap.key ? '' : cap.key)}
                />
              ))}
            </div>
          </div>
        </section>
      )
    }
    components/ui/kinematics/capability-reveals.tsx
    'use client'
    
    import { useEffect, useRef, useState } from 'react'
    
    /* The small animations that play inside a Capabilities Accordion row as it
       opens — one per kind of work. Each is driven purely by `open`. */
    
    export function ClipReveal({ open }: { open: boolean }) {
      return (
        <div className="relative h-40 w-full overflow-hidden rounded-xl bg-black/20 md:h-full md:w-56">
          <div
            className="absolute inset-0 bg-gradient-to-br from-kine-primary/30 via-kine-primary/10 to-transparent transition-[clip-path] duration-700 ease-out"
            style={{ clipPath: open ? 'inset(0% 0% 0% 0%)' : 'inset(100% 0% 0% 0%)' }}
          />
          <div
            className="absolute inset-4 rounded-lg border border-kine-primary/40 transition-[clip-path] delay-150 duration-700 ease-out"
            style={{ clipPath: open ? 'inset(0% 0% 0% 0%)' : 'inset(0% 100% 0% 0%)' }}
          />
        </div>
      )
    }
    
    export function CodeReveal({ open }: { open: boolean }) {
      const lines = ['export const revalidate = 3600', 'export default async function Page() {', '  const data = await get()', '  return <Fast data={data} />', '}']
      return (
        <div className="h-40 w-full overflow-hidden rounded-xl border border-white/10 bg-black/30 p-4 font-mono text-[11px] leading-relaxed text-kine-primary/80 md:h-full md:w-56">
          {lines.map((line, i) => (
            <div
              key={line}
              className="whitespace-pre transition-all duration-500 ease-out"
              style={{
                opacity: open ? 1 : 0,
                transform: open ? 'translateX(0)' : 'translateX(-12px)',
                transitionDelay: `${i * 90}ms`,
              }}
            >
              {line}
            </div>
          ))}
        </div>
      )
    }
    
    export function DrawReveal({ open }: { open: boolean }) {
      const pathRef = useRef<SVGPathElement>(null)
      const [total, setTotal] = useState(0)
    
      useEffect(() => {
        if (pathRef.current) setTotal(pathRef.current.getTotalLength())
      }, [])
    
      return (
        <div className="flex h-40 w-full items-center justify-center rounded-xl bg-black/20 md:h-full md:w-56">
          <svg viewBox="0 0 200 80" className="h-16 w-40" fill="none">
            <path
              ref={pathRef}
              d="M4 60 C 40 60, 40 12, 76 12 S 112 60, 148 60 S 176 20, 196 20"
              stroke="#ceff00"
              strokeWidth={3}
              strokeLinecap="round"
              style={{
                strokeDasharray: total,
                strokeDashoffset: open ? 0 : total,
                transition: 'stroke-dashoffset 900ms ease-out',
              }}
            />
          </svg>
        </div>
      )
    }
    
    export function NetworkReveal({ open }: { open: boolean }) {
      const nodes = [
        { x: 20, y: 20 }, { x: 80, y: 12 }, { x: 50, y: 45 }, { x: 12, y: 70 }, { x: 85, y: 68 },
      ]
      const edges = [[0, 2], [1, 2], [2, 3], [2, 4]]
      return (
        <div className="flex h-40 w-full items-center justify-center rounded-xl bg-black/20 md:h-full md:w-56">
          <svg viewBox="0 0 100 80" className="h-24 w-full max-w-[180px]" fill="none">
            {edges.map(([a, b], i) => (
              <line
                key={i}
                x1={nodes[a].x} y1={nodes[a].y} x2={nodes[b].x} y2={nodes[b].y}
                stroke="#ceff00"
                strokeOpacity={0.5}
                strokeWidth={1}
                style={{
                  opacity: open ? 1 : 0,
                  transition: `opacity 400ms ease-out ${i * 80}ms`,
                }}
              />
            ))}
            {nodes.map((n, i) => (
              <circle
                key={i}
                cx={n.x} cy={n.y} r={4}
                fill="#ceff00"
                style={{
                  opacity: open ? 1 : 0,
                  transform: open ? 'scale(1)' : 'scale(0)',
                  transformOrigin: `${n.x}px ${n.y}px`,
                  transition: `opacity 300ms ease-out ${i * 70}ms, transform 300ms ease-out ${i * 70}ms`,
                }}
              />
            ))}
          </svg>
        </div>
      )
    }
    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

PropTypeDefaultDescription
headingstring"What We Do"Section title.
capabilitiesCapability[]4 rowsEach has a `key`, `number`, `title`, `teaser`, `body` and a `reveal`: clip, code, draw or network.