Design Library

Product hero - cinematic orbit

kalunza-product-hero-cinematic-orbit.tsx

componentskill: emil-design-eng + design-taste-frontend + scroll-world + gsap + motion-design + threejs-r3fheroecommerceproductcinematicluxurydark

Brief

The prompt that produced this. Re-run it to generate more in the same taste.

Aesthetic
cinematic craft atlas - nocturnal, tactile, sculptural, asymmetric, grounded, luminous, African-modern
Reference
Kalunza passport-holder storyboard and multi-angle product photography, 2026-07-24
Intent
make one travel object feel like a world worth entering while keeping the product easy to understand and buy
Guardrails
always keep the product recognisable, use one muted emerald accent, hold a single dark theme, keep the primary action legible, collapse cleanly at 375px
never use AI-purple glow, use beige-and-brass luxury defaults, add scroll instructions, use em dashes, animate without a storytelling or feedback purpose

Install

pnpm dlx shadcn@latest add http://localhost:3040/r/kalunza-product-hero-cinematic-orbit.json

Source

Meta block and library type import stripped - this is what pastes cleanly elsewhere.

'use client'

import { useState } from 'react'

const defaultImage =
  'https://cdn.shopify.com/s/files/1/1026/5893/3104/files/FLASH1_673ff8c9-ef62-474d-8e7d-ad41cbaaff61.jpg?v=1783655598'

type ProductHeroProps = {
  brand?: string
  imageUrl?: string
  headline?: string
  body?: string
  shopHref?: string
}

const chapters = ['Journey', 'Hands', 'Pattern', 'Inside', 'Piece']

export default function KalunzaProductHeroCinematicOrbit({
  brand = 'Kalunza',
  imageUrl = defaultImage,
  headline = 'Made to move with you.',
  body = 'A travel companion shaped by Soweto craft and a pattern that refuses to disappear.',
  shopHref = 'https://kalunza.com/collections/all',
}: ProductHeroProps) {
  const [activeChapter, setActiveChapter] = useState(0)

  return (
    <main id="top" className="relative min-h-[100dvh] overflow-hidden bg-[#080b09] text-[#f1f4ee]">
      <img
        src={imageUrl}
        alt="Patterned travel accessory"
        width={1600}
        height={1200}
        loading="eager"
        fetchPriority="high"
        className="absolute inset-0 size-full object-cover object-[68%_50%]"
      />
      <div
        aria-hidden
        className="absolute inset-0 bg-[linear-gradient(90deg,#080b09_0%,rgba(8,11,9,.94)_37%,rgba(8,11,9,.42)_67%,rgba(8,11,9,.08)_100%),linear-gradient(0deg,rgba(4,6,5,.36),transparent_38%)]"
      />

      <header className="relative z-20 mx-auto flex min-h-18 max-w-[1600px] items-center justify-between gap-5 px-5 py-4 sm:px-8 lg:px-14">
        <a
          href="#top"
          className="flex items-center gap-3 font-semibold text-sm uppercase tracking-[0.22em] transition-[transform,color] duration-[160ms] ease-[cubic-bezier(0.23,1,0.32,1)] active:scale-[0.97] motion-reduce:transform-none motion-reduce:transition-none"
        >
          <span aria-hidden className="h-7 w-[17px] rounded-full bg-[#78947a]" />
          {brand}
        </a>

        <nav
          aria-label="Product story"
          className="hidden items-center gap-1 rounded-full border border-white/15 bg-[#0e1210]/80 p-1 backdrop-blur-xl md:flex"
        >
          {chapters.map((chapter, index) => (
            <button
              key={chapter}
              type="button"
              aria-pressed={activeChapter === index}
              onClick={() => setActiveChapter(index)}
              className={`rounded-full px-4 py-2 text-xs transition-[transform,background-color,color] duration-[160ms] ease-[cubic-bezier(0.23,1,0.32,1)] active:scale-[0.97] motion-reduce:transform-none motion-reduce:transition-none ${
                activeChapter === index
                  ? 'bg-[#c9d8c7] text-[#09100b]'
                  : 'text-[#aeb6ae] hover:text-[#f1f4ee]'
              }`}
            >
              {chapter}
            </button>
          ))}
        </nav>

        <a
          href={shopHref}
          className="hidden whitespace-nowrap rounded-full bg-[#dce9da] px-5 py-3 font-semibold text-[#08100b] text-sm transition-[transform,background-color] duration-[160ms] ease-[cubic-bezier(0.23,1,0.32,1)] hover:bg-[#eef5ec] active:scale-[0.97] motion-reduce:transform-none motion-reduce:transition-none sm:inline-flex"
        >
          Shop the holder
        </a>
      </header>

      <section className="relative z-10 flex min-h-[calc(100dvh-72px)] items-end px-5 pb-28 sm:px-8 md:items-center md:pb-10 lg:px-14">
        <div className="w-full max-w-[530px]">
          <h1 className="max-w-[10ch] text-balance font-medium text-[clamp(2.4rem,5.35vw,5.35rem)] leading-[0.98] tracking-[-0.055em]">
            {headline}
          </h1>
          <p className="mt-5 max-w-[31ch] text-[#d1d7d0] text-[clamp(1rem,1.22vw,1.14rem)] leading-[1.56]">
            {body}
          </p>
          <div className="mt-6 flex flex-wrap gap-2">
            {['Made in South Africa', 'Small-batch craft'].map((item) => (
              <span
                key={item}
                className="rounded-full border border-[#97b69b]/35 bg-[#12261d]/70 px-3.5 py-2 font-semibold text-[#dce6dc] text-xs backdrop-blur-lg"
              >
                {item}
              </span>
            ))}
          </div>
          <a
            href={shopHref}
            className="mt-7 inline-flex whitespace-nowrap rounded-full bg-[#dce9da] px-6 py-3.5 font-semibold text-[#08100b] text-sm transition-[transform,background-color] duration-[160ms] ease-[cubic-bezier(0.23,1,0.32,1)] hover:bg-[#eef5ec] active:scale-[0.97] motion-reduce:transform-none motion-reduce:transition-none sm:hidden"
          >
            Shop the holder
          </a>
        </div>
      </section>

      <div
        role="group"
        aria-label={`Chapter ${activeChapter + 1} of ${chapters.length}: ${chapters[activeChapter]}`}
        className="absolute top-1/2 right-6 z-20 hidden -translate-y-1/2 flex-col items-center gap-4 lg:flex"
      >
        {chapters.map((chapter, index) => (
          <button
            key={chapter}
            type="button"
            aria-label={`Show ${chapter}`}
            onClick={() => setActiveChapter(index)}
            className="grid size-7 place-items-center rounded-full transition-transform duration-[160ms] ease-[cubic-bezier(0.23,1,0.32,1)] active:scale-[0.97] motion-reduce:transform-none motion-reduce:transition-none"
          >
            <span
              aria-hidden
              className={`block rounded-full bg-[#78947a] transition-[width,height,opacity] duration-[160ms] ease-[cubic-bezier(0.23,1,0.32,1)] ${
                activeChapter === index ? 'size-3 opacity-100' : 'size-1.5 opacity-50'
              }`}
            />
          </button>
        ))}
      </div>
    </main>
  )
}