The default counter over a studio's home page. The nav, headline, links and photo each rise as the curtain clears them. Press reload in the address bar to play it again.
@keyframeui
registry/demos/counter-curtain-demo.tsx
"use client";
import * as React from "react";
import { CounterCurtain, CounterCurtainLine } from "@/components/ui/counter-curtain";
const photo =
"https://images.unsplash.com/photo-1600585154340-be6161a56a0c?auto=format&fit=crop&w=900&h=1100&q=80";
// The reload button plays it again by remounting it with a new key. On a real
// site, drop `contained` and `once={false}`: it covers the screen, once per
// visit.
export function CounterCurtainDemo() {
const [run, setRun] = React.useState(0);
return (
<div className="flex h-[var(--demo-height,36rem)] w-full flex-col overflow-hidden rounded-xl border border-zinc-200 bg-[#f3f1ec] text-zinc-950 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50">
<BrowserBar url="okto.studio" run={run} onReload={() => setRun((r) => r + 1)} />
<CounterCurtain key={run} contained once={false} className="@container min-h-0 flex-1">
<div className="flex h-full flex-col px-5 pt-4 pb-5 sm:px-8 sm:pt-5 sm:pb-7">
<header className="flex items-center justify-between text-sm">
<CounterCurtainLine className="font-semibold tracking-tight">Okto°</CounterCurtainLine>
<nav className="hidden gap-6 text-zinc-600 @md:flex dark:text-zinc-400">
{["Work", "Studio", "Journal"].map((item) => (
<CounterCurtainLine key={item}>{item}</CounterCurtainLine>
))}
</nav>
<CounterCurtainLine>Contact</CounterCurtainLine>
</header>
<div className="mt-auto flex items-end gap-8">
<div className="min-w-0 flex-1">
<CounterCurtainLine className="text-xs tracking-[0.18em] text-zinc-500 uppercase">
Independent design studio, Oslo
</CounterCurtainLine>
<h1 className="mt-4 text-[clamp(2.75rem,10cqw,7rem)] leading-[0.92] font-semibold tracking-[-0.05em]">
<CounterCurtainLine>Brands that</CounterCurtainLine>
<CounterCurtainLine>move before</CounterCurtainLine>
<CounterCurtainLine>they speak.</CounterCurtainLine>
</h1>
<div className="mt-6 flex flex-wrap items-end justify-between gap-x-8 gap-y-4 sm:mt-8">
<CounterCurtainLine className="max-w-xs text-sm leading-relaxed text-pretty text-zinc-600 dark:text-zinc-400">
Identity, websites and motion for companies that would rather be felt
than explained.
</CounterCurtainLine>
<CounterCurtainLine>
<a
href="#work"
className="group inline-flex items-center gap-2 text-sm font-medium underline decoration-zinc-400 underline-offset-4 transition-colors hover:decoration-current"
>
See the work
<span aria-hidden className="transition-transform duration-200 group-hover:translate-x-0.5">
→
</span>
</a>
</CounterCurtainLine>
</div>
</div>
{/* An image rises out of its frame like a line does. */}
<CounterCurtainLine className="hidden w-[30%] shrink-0 rounded-lg @2xl:block">
<img
src={photo}
alt="A low timber and glass house lit up at dusk"
className="aspect-[4/5] w-full rounded-lg object-cover"
/>
</CounterCurtainLine>
</div>
</div>
</CounterCurtain>
</div>
);
}
function BrowserBar({ url, run, onReload }: { url: string; run: number; onReload: () => void }) {
return (
<div className="flex h-10 shrink-0 items-center gap-3 border-b border-zinc-200 bg-white/60 px-3 dark:border-zinc-800 dark:bg-zinc-900/60">
<div aria-hidden className="flex gap-1.5">
{[0, 1, 2].map((dot) => (
<span key={dot} className="size-2.5 rounded-full bg-zinc-300 dark:bg-zinc-700" />
))}
</div>
<button
type="button"
onClick={onReload}
aria-label="Reload to play it again"
title="Play again"
className="grid size-7 place-items-center rounded-md text-zinc-500 transition-[background-color,color,scale] duration-150 hover:bg-zinc-200/70 hover:text-zinc-900 active:scale-[0.94] dark:hover:bg-zinc-800 dark:hover:text-zinc-100"
>
<svg
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
className="size-3.5 transition-[rotate] duration-500 ease-[cubic-bezier(0.22,1,0.36,1)]"
style={{ rotate: `${run * 360}deg` }}
>
<path d="M13.5 8a5.5 5.5 0 1 1-1.6-3.9" />
<path d="M13.5 2.5v3h-3" />
</svg>
</button>
<span className="mx-auto truncate rounded-md bg-zinc-100 px-3 py-1 font-mono text-[11px] text-zinc-500 dark:bg-zinc-800/80 dark:text-zinc-400">
{url}
</span>
<span aria-hidden className="w-[4.75rem]" />
</div>
);
}
Live preview, examples and props: https://keyframeui.com/components/counter-curtain#counter-curtain-demo




























