The default counter set in serif on a stone curtain that slides off to the left. It waits for the three photos to load and decode, so they're sharp from the first frame.
@keyframeui
registry/demos/counter-curtain-editorial-demo.tsx
"use client";
import * as React from "react";
import { CounterCurtain, CounterCurtainLine } from "@/components/ui/counter-curtain";
const image = (id: string) =>
`https://images.unsplash.com/photo-${id}?auto=format&fit=crop&w=900&h=1100&q=80`;
const rooms = [
{ id: "1618221195710-dd6b41faaea6", alt: "A long open living room with a sofa, coffee table and wide windows", caption: "The long room" },
{ id: "1586023492125-27b2c045efd7", alt: "A yellow armchair beside a floor lamp against a white wall", caption: "Reading corner" },
{ id: "1555041469-a586c61ea9bc", alt: "A green velvet sofa on a pale wooden floor", caption: "The snug" },
];
// The default counter, restyled with curtainClassName, leaving to the left.
// It waits for the three photos, so they're decoded and sharp before anything
// is uncovered.
export function CounterCurtainEditorialDemo() {
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-stone-200 bg-stone-50 text-stone-900 dark:border-stone-800 dark:bg-stone-950 dark:text-stone-100">
<BrowserBar url="hollow-house.journal" run={run} onReload={() => setRun((r) => r + 1)} />
<CounterCurtain
key={run}
contained
once={false}
direction="left"
curtainClassName="bg-stone-200 font-serif text-stone-900 dark:bg-stone-800 dark:text-stone-100"
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-baseline justify-between border-b border-stone-300/70 pb-3 text-sm dark:border-stone-700/70">
<CounterCurtainLine className="font-serif text-lg italic">Hollow House</CounterCurtainLine>
<CounterCurtainLine className="text-xs tracking-[0.18em] text-stone-500 uppercase">
Issue 12 · Autumn
</CounterCurtainLine>
</header>
<div className="mt-6 grid min-h-0 flex-1 gap-6 @2xl:grid-cols-[1fr_1.35fr] @2xl:gap-10">
<div className="flex flex-col justify-end">
<h1 className="font-serif text-[clamp(2.5rem,7.5cqw,5.75rem)] leading-[0.95] tracking-[-0.03em]">
<CounterCurtainLine>Rooms that</CounterCurtainLine>
<CounterCurtainLine>hold the</CounterCurtainLine>
<CounterCurtainLine className="italic">light.</CounterCurtainLine>
</h1>
<CounterCurtainLine className="mt-5 max-w-xs text-sm leading-relaxed text-pretty text-stone-600 dark:text-stone-400">
A 1930s terrace in Bergen, opened up from the front door to the garden
and furnished one chair at a time.
</CounterCurtainLine>
</div>
<div className="hidden min-h-0 grid-cols-3 items-end gap-3 @2xl:grid">
{rooms.map((room, index) => (
<figure key={room.id} className={index === 1 ? "pb-10" : undefined}>
<CounterCurtainLine className="rounded-md">
<img
src={image(room.id)}
alt={room.alt}
className="aspect-[4/5] w-full rounded-md object-cover"
/>
</CounterCurtainLine>
<CounterCurtainLine className="mt-2 font-serif text-xs text-stone-500 italic">
{room.caption}
</CounterCurtainLine>
</figure>
))}
</div>
</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-stone-200 bg-white/60 px-3 dark:border-stone-800 dark:bg-stone-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-stone-300 dark:bg-stone-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-stone-500 transition-[background-color,color,scale] duration-150 hover:bg-stone-200/70 hover:text-stone-900 active:scale-[0.94] dark:hover:bg-stone-800 dark:hover:text-stone-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-stone-100 px-3 py-1 font-mono text-[11px] text-stone-500 dark:bg-stone-800/80 dark:text-stone-400">
{url}
</span>
<span aria-hidden className="w-[4.75rem]" />
</div>
);
}
Live preview, examples and props: https://keyframeui.com/components/counter-curtain#counter-curtain-editorial-demo



























