UNPKG

lightswind

Version:

A collection of beautifully crafted React Components, Blocks & Templates for Modern Developers. Create stunning web applications effortlessly by using our 160+ professional and animated react components.

123 lines 10.3 kB
"use strict"; "use client"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HangingIdCard = void 0; const jsx_runtime_1 = require("react/jsx-runtime"); const react_1 = require("react"); const utils_1 = require("@/components/lib/utils"); // ─── Physics constants ──────────────────────────────────────────────────────── // ─── Physics constants ──────────────────────────────────────────────────────── const SPRING_K = 0; // No spring! A real pendulum relies only on gravity const DAMPING = 0.9; // Light air resistance so it swings naturally const GRAVITY = 3000; // Gravity scalar for satisfying snappy momentum const MASS = 1; // ─── SVG Thick Lanyard / Ribbon ────────────────────────────────────────────────────── const Lanyard = ({ length, color }) => { return ((0, jsx_runtime_1.jsxs)("svg", { width: "30", height: length, viewBox: `0 0 30 ${length}`, style: { display: "block", margin: "0 auto", overflow: "visible" }, children: [(0, jsx_runtime_1.jsx)("circle", { cx: "15", cy: "0", r: "5", fill: color }), (0, jsx_runtime_1.jsx)("path", { d: `M 13 0 L 10 ${length}`, stroke: color, strokeWidth: "6", opacity: "0.9" }), (0, jsx_runtime_1.jsx)("path", { d: `M 17 0 L 20 ${length}`, stroke: color, strokeWidth: "6", opacity: "0.9" }), (0, jsx_runtime_1.jsx)("rect", { x: "10", y: length - 6, width: "10", height: "8", rx: "2", fill: "#94a3b8" }), (0, jsx_runtime_1.jsx)("circle", { cx: "15", cy: length + 2, r: "3", fill: "#e2e8f0" })] })); }; // ─── Main Component ─────────────────────────────────────────────────────────── const HangingIdCard = ({ children, ropeLength = 130, // Increased height for a more prominent drop ropeColor = "#4a5568", className, name = "John Lightswind", role = "UI Developer", badgeId = "LW-2025", accentColor = "#173eff", }) => { const physRef = (0, react_1.useRef)({ angle: 0, vel: 0 }); const rafRef = (0, react_1.useRef)(null); const prevTimeRef = (0, react_1.useRef)(null); const prevAngleRef = (0, react_1.useRef)(0); const isDraggingRef = (0, react_1.useRef)(false); const [angle, setAngle] = (0, react_1.useState)(0); const [isDragState, setIsDragState] = (0, react_1.useState)(false); const dragStartX = (0, react_1.useRef)(0); const dragAngle0 = (0, react_1.useRef)(0); // ── Physics loop ──────────────────────────────────────────────────────────── const tick = (0, react_1.useCallback)((now) => { if (prevTimeRef.current === null) { prevTimeRef.current = now; } const dt = Math.min((now - prevTimeRef.current) / 1000, 0.05); // cap at 50ms prevTimeRef.current = now; const s = physRef.current; if (!isDraggingRef.current) { // Realistic pendulum: L is approximate center of mass const L = ropeLength + 100; const torque = -(GRAVITY / L) * Math.sin(s.angle) - (DAMPING / MASS) * s.vel - (SPRING_K / MASS) * s.angle; s.vel += torque * dt; s.angle += s.vel * dt; setAngle(s.angle); if (Math.abs(s.angle) > 0.001 || Math.abs(s.vel) > 0.001) { rafRef.current = requestAnimationFrame(tick); } else { // settled perfectly at bottom s.angle = 0; s.vel = 0; setAngle(0); } } else { // Track velocity while dragging so we can "flick" it if (dt > 0) { s.vel = (s.angle - prevAngleRef.current) / dt; } prevAngleRef.current = s.angle; rafRef.current = requestAnimationFrame(tick); } }, [ropeLength]); const startPhysics = (0, react_1.useCallback)(() => { if (rafRef.current) cancelAnimationFrame(rafRef.current); prevTimeRef.current = null; rafRef.current = requestAnimationFrame(tick); }, [tick]); // ── Pointer events ────────────────────────────────────────────────────────── const onPointerDown = (0, react_1.useCallback)((e) => { e.currentTarget.setPointerCapture(e.pointerId); isDraggingRef.current = true; setIsDragState(true); dragStartX.current = e.clientX; dragAngle0.current = physRef.current.angle; prevAngleRef.current = physRef.current.angle; if (rafRef.current) cancelAnimationFrame(rafRef.current); prevTimeRef.current = null; rafRef.current = requestAnimationFrame(tick); }, [tick]); const onPointerMove = (0, react_1.useCallback)((e) => { if (!isDraggingRef.current) return; const dx = e.clientX - dragStartX.current; const L = ropeLength + 100; // angle = asin(dx / L) but clamped. Subtracted to match mouse drag direction for hanging pendulum. const newAngle = dragAngle0.current - dx / L; const clamped = Math.max(-1.4, Math.min(1.4, newAngle)); physRef.current.angle = clamped; setAngle(clamped); }, [ropeLength]); const onPointerUp = (0, react_1.useCallback)((e) => { e.currentTarget.releasePointerCapture(e.pointerId); isDraggingRef.current = false; setIsDragState(false); }, []); // ── Click impulse (tap) ───────────────────────────────────────────────────── const onCardClick = (0, react_1.useCallback)(() => { if (Math.abs(physRef.current.vel) < 0.1 && Math.abs(physRef.current.angle) < 0.05) { physRef.current.vel = 4.0; // Give it a satisfying push startPhysics(); } }, [startPhysics]); (0, react_1.useEffect)(() => () => { if (rafRef.current) cancelAnimationFrame(rafRef.current); }, []); const cardRotateDeg = angle * (180 / Math.PI); return ((0, jsx_runtime_1.jsxs)("div", { className: (0, utils_1.cn)("flex flex-col items-center select-none", className), style: { touchAction: "none" }, children: [(0, jsx_runtime_1.jsx)("div", { className: "w-3 h-3 rounded-full shadow-md z-10 relative", style: { background: accentColor } }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col items-center cursor-grab active:cursor-grabbing", onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: onPointerUp, onClick: onCardClick, style: { transform: `rotate(${cardRotateDeg}deg)`, transformOrigin: "top center", willChange: "transform", marginTop: "-6px" // slight overlap with anchor }, children: [(0, jsx_runtime_1.jsx)("div", { style: { pointerEvents: "none" }, children: (0, jsx_runtime_1.jsx)(Lanyard, { length: ropeLength, color: ropeColor }) }), (0, jsx_runtime_1.jsx)("div", { className: "relative w-52 rounded-2xl overflow-hidden shadow-2xl border border-white/20 dark:border-white/10 bg-white dark:bg-zinc-900 pointer-events-none mt-[-2px]", children: children ?? ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col h-full", children: [(0, jsx_runtime_1.jsxs)("div", { className: "px-4 py-3 flex flex-col items-center gap-1", style: { background: `linear-gradient(135deg, ${accentColor} 0%, #3758f9 100%)` }, children: [(0, jsx_runtime_1.jsx)("p", { className: "text-[9px] font-bold tracking-[0.25em] text-white/70 uppercase", children: "Lightswind UI" }), (0, jsx_runtime_1.jsx)("div", { className: "flex h-14 w-14 items-center justify-center rounded-xl bg-white/20 backdrop-blur-sm mt-1 shadow-inner", children: (0, jsx_runtime_1.jsx)("img", { src: "https://firebasestorage.googleapis.com/v0/b/codewithmuhilandb.appspot.com/o/uploads%2Flightwind-logo.png?alt=media&token=6ba956f1-994c-46ca-9eda-6e46b5662eb9", alt: "logo", className: "h-9 w-9 object-contain filter brightness-0 invert" }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "bg-white dark:bg-zinc-900 px-4 py-4 flex flex-col items-center gap-2 flex-1", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm font-bold text-zinc-900 dark:text-white text-center leading-tight", children: name }), (0, jsx_runtime_1.jsx)("p", { className: "text-[11px] text-zinc-500 dark:text-zinc-400 font-medium", children: role }), (0, jsx_runtime_1.jsx)("div", { className: "my-2 w-full border-t border-zinc-100 dark:border-zinc-800" }), (0, jsx_runtime_1.jsx)("div", { className: "flex gap-[2px] items-end h-7 px-1", children: Array.from({ length: 28 }).map((_, i) => ((0, jsx_runtime_1.jsx)("div", { className: "bg-zinc-800 dark:bg-zinc-200 rounded-[1px]", style: { width: i % 3 === 0 ? "3px" : "1.5px", height: `${50 + Math.sin(i * 1.3) * 35}%`, } }, i))) }), (0, jsx_runtime_1.jsx)("p", { className: "text-[10px] font-mono font-bold mt-1 tracking-widest", style: { color: accentColor }, children: badgeId }), (0, jsx_runtime_1.jsx)("div", { className: "mt-1 px-3 py-0.5 rounded-full text-[9px] font-bold text-white uppercase tracking-widest", style: { background: accentColor }, children: "ACTIVE" })] })] })) })] }), (0, jsx_runtime_1.jsx)("p", { className: "mt-8 text-[11px] text-zinc-400 dark:text-zinc-600 font-medium select-none pointer-events-none", children: "Drag or click the card" })] })); }; exports.HangingIdCard = HangingIdCard; exports.default = exports.HangingIdCard; //# sourceMappingURL=HangingIdCard.js.map