Signature
An animated SVG signature effect that draws out text as if hand-written.
Live preview recorded from Dqnamo Kitchen; component and provider source published by JP / dqnamo. Preview: platform recorded · rights authorized.
A perforated ticket silhouette with composable sections and subtle parallax depth.
dSource LanddqnamoWhy it stands out
A reusable ticket silhouette combines perforated edges, composable content sections, and a subtle layered depth effect. Pointer movement shifts the artwork and surface lighting to reinforce the object-like presentation.
"use client";
import {
type CSSProperties,
type ReactNode,
useSyncExternalStore,
} from "react";
import type { ReactParallaxTiltProps } from "react-parallax-tilt";
import Tilt from "react-parallax-tilt";
import { cn } from "@/helpers/classname-helper";
const hoverTiltMedia =
"(hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference)";
const ticketClipPath = `polygon(
0 var(--ticket-corner-size),
var(--ticket-corner-size) 0,
calc(100% - var(--ticket-corner-size)) 0,
100% var(--ticket-corner-size),
100% calc(100% - var(--ticket-stub-height) - var(--ticket-notch-size)),
calc(100% - var(--ticket-notch-size)) calc(100% - var(--ticket-stub-height)),
100% calc(100% - var(--ticket-stub-height) + var(--ticket-notch-size)),
100% calc(100% - var(--ticket-corner-size)),
calc(100% - var(--ticket-corner-size)) 100%,
var(--ticket-corner-size) 100%,
0 calc(100% - var(--ticket-corner-size)),
0 calc(100% - var(--ticket-stub-height) + var(--ticket-notch-size)),
var(--ticket-notch-size) calc(100% - var(--ticket-stub-height)),
0 calc(100% - var(--ticket-stub-height) - var(--ticket-notch-size))
)`;
const ticketBackground =
"linear-gradient(145deg, rgb(255 255 255 / 10%), transparent 42%), var(--ticket-paper)";
const ticketShadow =
"drop-shadow(0 1px 1px rgb(15 15 15 / 10%)) drop-shadow(0 18px 26px rgb(15 15 15 / 13%))";
type TicketCustomProperty =
| "--ticket-corner-size"
| "--ticket-ink"
| "--ticket-notch-size"
| "--ticket-paper"
| "--ticket-stub-height";
type TicketRootStyle = CSSProperties &
Partial<Record<TicketCustomProperty, string>>;
export type TicketTiltProps = Omit<
ReactParallaxTiltProps,
"children" | "className" | "style"
>;
type CssLength = number | string;
export type TicketProps = {
body: ReactNode;
stub: ReactNode;
"aria-hidden"?: boolean;
"aria-label"?: string;
bodyClassName?: string;
className?: string;
cornerSize?: CssLength;
ink?: string;
notchSize?: CssLength;
paper?: string;
stubClassName?: string;
stubHeight?: CssLength;
style?: CSSProperties;
ticketClassName?: string;
tilt?: boolean;
tiltProps?: TicketTiltProps;
};
function subscribeToHoverTilt(listener: () => void) {
const media = window.matchMedia(hoverTiltMedia);
media.addEventListener("change", listener);
return () => media.removeEventListener("change", listener);
}
function getHoverTiltSnapshot() {
return window.matchMedia(hoverTiltMedia).matches;
}
function getServerHoverTiltSnapshot() {
return false;
}
function toCssLength(value: CssLength) {
return typeof value === "number" ? `${value}px` : value;
}
export function Ticket({
"aria-hidden": ariaHidden,
"aria-label": ariaLabel,
body,
bodyClassName,
className,
cornerSize = 0,
ink = "#171714",
notchSize = 13,
paper = "#ff633f",
stub,
stubClassName,
stubHeight = "24%",
style,
ticketClassName,
tilt = true,
tiltProps,
}: TicketProps) {
const hoverTiltEnabled = useSyncExternalStore(
subscribeToHoverTilt,
getHoverTiltSnapshot,
getServerHoverTiltSnapshot,
);
const {
glareEnable = false,
scale = 1.018,
tiltEnable: tiltEnabledByProp = true,
...resolvedTiltProps
} = tiltProps ?? {};
const tiltEnabled = tilt && tiltEnabledByProp && hoverTiltEnabled;
const glareEnabled = tiltEnabled && glareEnable;
const rootStyle: TicketRootStyle = {
"--ticket-corner-size": toCssLength(cornerSize),
"--ticket-ink": ink,
"--ticket-notch-size": toCssLength(notchSize),
"--ticket-paper": paper,
"--ticket-stub-height": toCssLength(stubHeight),
filter: ticketShadow,
...style,
};
return (
<div
className={cn("relative aspect-[5/12] w-[min(100%,17rem)]", className)}
style={rootStyle}
>
<Tilt
glareEnable={glareEnabled}
gyroscope={false}
key={glareEnabled ? "ticket-with-glare" : "ticket-without-glare"}
perspective={1100}
tiltMaxAngleX={6}
tiltMaxAngleY={6}
transitionEasing="cubic-bezier(0.23, 1, 0.32, 1)"
transitionSpeed={220}
{...resolvedTiltProps}
className="relative h-full w-full touch-pan-y [transform-style:preserve-3d]"
scale={tiltEnabled ? scale : 1}
style={{
clipPath: ticketClipPath,
}}
tiltEnable={tiltEnabled}
>
<article
aria-hidden={ariaHidden}
aria-label={ariaHidden ? undefined : ariaLabel}
className={cn(
"relative grid h-full w-full grid-rows-[minmax(0,1fr)_var(--ticket-stub-height)] overflow-hidden text-[var(--ticket-ink)] transition-[color,background-color] duration-[180ms] ease-[ease]",
ticketClassName,
)}
style={{ background: ticketBackground }}
>
<div
className={cn(
"relative min-h-0 min-w-0 overflow-hidden",
bodyClassName,
)}
>
{body}
<span
aria-hidden="true"
className="pointer-events-none absolute right-[calc(var(--ticket-notch-size)+6px)] bottom-px left-[calc(var(--ticket-notch-size)+6px)] z-[2] border-current border-b border-dashed opacity-30"
/>
</div>
<div
className={cn(
"relative min-h-0 min-w-0 overflow-hidden",
stubClassName,
)}
>
{stub}
</div>
</article>
</Tilt>
</div>
);
}Discovery vocabulary
Related by governed terms
An animated SVG signature effect that draws out text as if hand-written.
More from dqnamo