Blueprint Frame
A drafting-style frame that draws itself on first paint, then drifts an ambient diagonal hatch forever.
Two framing bands, full-width rules, and registration crosshairs draw themselves in on first paint, then the hatch drifts as an ambient current. Every step is CSS keyframes, so it runs off the main thread. Reduced motion collapses the whole sequence to a single opacity fade.
Installation
Complete the shared Setup first, then copy the component into
components/ui/blueprint-frame.tsx.
'use client';import * as React from 'react';import { cn } from '@/lib/cn';export type BlueprintFrameProps = { color?: string; inset?: number; speed?: number; mask?: boolean; className?: string;};const EO = 'cubic-bezier(0.23, 1, 0.32, 1)';const HATCH = 'repeating-linear-gradient(-63deg, color-mix(in srgb, var(--bp) 12%, transparent) 0, color-mix(in srgb, var(--bp) 12%, transparent) 1px, transparent 1px, transparent 14px)';const CSS = `@keyframes bp-draw-y { from { clip-path: inset(0 0 100% 0); } to { clip-path: inset(0 0 0 0); } }@keyframes bp-draw-x { from { clip-path: inset(0 50% 0 50%); } to { clip-path: inset(0 0 0 0); } }@keyframes bp-band-in { from { opacity: 0; filter: blur(5px); } to { opacity: 1; filter: blur(0); } }@keyframes bp-cross-in { from { opacity: 0; transform: translate(-50%,-50%) scale(0.5); } to { opacity: 1; transform: translate(-50%,-50%) scale(1); } }@keyframes bp-drift { to { background-position: 12.5px -6.4px; } }@keyframes bp-fade { from { opacity: 0; } to { opacity: 1; } }@media (prefers-reduced-motion: reduce) { .bp-anim, .bp-cross { animation: bp-fade 260ms ease both !important; clip-path: none !important; filter: none !important; } .bp-cross { transform: translate(-50%, -50%) !important; }}`;function Cross({ x, y, delay,}: { x: React.CSSProperties; y: React.CSSProperties['top']; delay: number;}) { const bar = 'color-mix(in srgb, var(--bp) 50%, transparent)'; return ( <div className="bp-cross absolute h-3.5 w-3.5" style={{ ...x, top: y, transform: 'translate(-50%, -50%)', animation: `bp-cross-in 450ms ${EO} ${delay}ms both`, }} > <span className="absolute left-1/2 top-0 h-full w-px -translate-x-1/2" style={{ background: bar }} /> <span className="absolute top-1/2 left-0 h-px w-full -translate-y-1/2" style={{ background: bar }} /> </div> );}export function BlueprintFrame({ color = '#ffffff', inset = 48, speed = 1, mask = false, className,}: BlueprintFrameProps) { const frozen = speed <= 0; const driftDur = frozen ? 0 : 7 / speed; const line = 'color-mix(in srgb, var(--bp) 10%, transparent)'; const bandH = 72; const gap = 40; const topTopY = gap; const topBotY = gap + bandH; const botTopY = `calc(100% - ${gap + bandH}px)`; const botBotY = `calc(100% - ${gap}px)`; const leftX: React.CSSProperties = { left: inset }; const rightX: React.CSSProperties = { left: `calc(100% - ${inset}px)` }; const bands = [ { top: topTopY, height: bandH }, { top: `calc(100% - ${gap + bandH}px)`, height: bandH }, ]; const rules = [topTopY, topBotY, botTopY, botBotY]; const crosses = [ { x: leftX, y: topTopY }, { x: rightX, y: topTopY }, { x: leftX, y: topBotY }, { x: rightX, y: topBotY }, { x: leftX, y: botTopY }, { x: rightX, y: botTopY }, { x: leftX, y: botBotY }, { x: rightX, y: botBotY }, ]; return ( <div aria-hidden className={cn( 'pointer-events-none absolute inset-0 select-none overflow-hidden', mask && '[mask-image:radial-gradient(ellipse_94%_100%_at_50%_46%,black_44%,transparent_98%)]', className, )} style={{ '--bp': color } as React.CSSProperties} > <style>{CSS}</style> <div className="bp-anim absolute inset-y-0 w-px" style={{ left: inset, background: line, animation: `bp-draw-y 720ms ${EO} both` }} /> <div className="bp-anim absolute inset-y-0 w-px" style={{ left: `calc(100% - ${inset}px)`, background: line, animation: `bp-draw-y 720ms ${EO} 90ms both` }} /> {bands.map((b, i) => ( <div key={i} className="bp-anim absolute overflow-hidden" style={{ left: inset, right: inset, top: b.top, height: b.height, backgroundImage: HATCH, animation: `bp-band-in 700ms ${EO} 340ms both` + (frozen ? '' : `, bp-drift ${driftDur}s linear 1040ms infinite`), }} /> ))} {rules.map((y, i) => ( <div key={i} className="bp-anim absolute inset-x-0 h-px" style={{ top: y, background: line, animation: `bp-draw-x 620ms ${EO} ${240 + i * 70}ms both` }} /> ))} {crosses.map((c, i) => ( <Cross key={i} x={c.x} y={c.y} delay={720 + i * 45} /> ))} </div> );}Usage
import { BlueprintFrame } from '@/components/ui/blueprint-frame';
export default function Example() {
return (
<div className="relative h-96 w-full overflow-hidden rounded-xl border border-black/[0.08] bg-[#fafafa] dark:border-white/10 dark:bg-[#08080a]">
<BlueprintFrame className="dark:hidden" color="#0a0a0a" />
<BlueprintFrame className="hidden dark:block" />
</div>
);
}Examples
A tighter inset pulls the vertical lines closer to center.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
color | string | "#ffffff" | Frame, hatch, and crosshair color, applied at graded alpha. |
inset | number | 48 | Inset of the two vertical frame lines from the edges, in px. |
speed | number | 1 | Ambient hatch drift speed. 0 freezes the hatch after the reveal. |
mask | boolean | false | Fade the edges with a radial mask. Off by default: the frame runs edge to edge. |
className | string | - | Forwarded to the root. |