Files
entscheidomat/components/coin-3d.tsx
2026-07-23 21:59:15 +02:00

75 lines
2.0 KiB
TypeScript

"use client";
import { forwardRef } from "react";
type Coin3DProps = {
frontLabel: string;
backLabel: string;
/** Kantenlänge in px */
size?: number;
/** Endlos-Idle-Drehung (Hero) */
spin?: boolean;
/**
* Achse, um die die Münze rotiert. Bestimmt, wie die Rückseite
* vorgedreht wird, damit ihr Text lesbar bleibt.
* "x" = Flip um die X-Achse (Tool), "y" = Drehung um die Y-Achse (Hero).
*/
spinAxis?: "x" | "y";
className?: string;
};
/** Gestapelte Kreisscheiben erzeugen die Münzdicke. */
const EDGE_LAYERS = [-3, -2, -1, 0, 1, 2, 3];
/**
* Echte 3D-CSS-Münze. Der forwarded ref zeigt auf das rotierende
* Innen-Element, damit GSAP `rotationX` flippen kann.
*/
const Coin3D = forwardRef<HTMLDivElement, Coin3DProps>(function Coin3D(
{ frontLabel, backLabel, size = 168, spin = false, spinAxis = "x", className = "" },
ref,
) {
return (
<div
ref={ref}
className={`coin ${spin ? "animate-coin-spin" : ""} ${className}`}
style={{ width: size, height: size }}
>
{EDGE_LAYERS.map((z) => (
<div
key={z}
aria-hidden
className="coin-edge"
style={{ transform: `translateZ(${z}px)` }}
/>
))}
<div className="coin-face coin-face--front">
<div className="coin-engraving" aria-hidden />
<div className="coin-engraving coin-engraving--inner" aria-hidden />
<span className="coin-label" style={{ fontSize: size * 0.1 }}>
{frontLabel}
</span>
</div>
<div
className="coin-face coin-face--back"
style={{
transform:
spinAxis === "y"
? "rotateY(180deg) translateZ(4px)"
: "rotateX(180deg) translateZ(4px)",
}}
>
<div className="coin-engraving" aria-hidden />
<div className="coin-engraving coin-engraving--inner" aria-hidden />
<span className="coin-label" style={{ fontSize: size * 0.1 }}>
{backLabel}
</span>
</div>
</div>
);
});
export default Coin3D;