서사형 덱빌딩 게임 프로토 타입

This commit is contained in:
Kyoung5seo
2026-06-15 23:10:54 +09:00
parent e1310bc872
commit e1f5cafdf1
49 changed files with 3261 additions and 7477 deletions

View File

@@ -0,0 +1,72 @@
import React, { useState, useEffect } from 'react';
import { motion, useMotionValue, useTransform, useAnimation } from 'framer-motion';
import './SwipeCard.css';
export default function SwipeCard({ card, onSwipe, phase }) {
const x = useMotionValue(0);
const controls = useAnimation();
// Dynamic friction based on phase
const friction = phase === 3 ? 1.5 : phase === 2 ? 1.2 : 0.8;
// Rotations and opacity
const rotate = useTransform(x, [-200, 200], [-15 * friction, 15 * friction]);
const leftOpacity = useTransform(x, [-100, -50, 0], [1, 0.5, 0]);
const rightOpacity = useTransform(x, [0, 50, 100], [0, 0.5, 1]);
const [exitX, setExitX] = useState(0);
// Reset when card changes
useEffect(() => {
setExitX(0);
x.set(0);
controls.start({ x: 0, opacity: 1, rotate: 0, transition: { duration: 0 } });
}, [card, controls, x]);
const handleDragEnd = async (event, info) => {
const threshold = 100;
if (info.offset.x > threshold) {
setExitX(300);
await controls.start({ x: 300, opacity: 0, transition: { duration: 0.3 } });
onSwipe('right');
} else if (info.offset.x < -threshold) {
setExitX(-300);
await controls.start({ x: -300, opacity: 0, transition: { duration: 0.3 } });
onSwipe('left');
} else {
// Snap back
controls.start({ x: 0, y: 0, transition: { type: 'spring', stiffness: 300, damping: 20 } });
}
};
return (
<div className="card-container">
<motion.div
className="swipe-card"
drag="x"
dragConstraints={{ left: 0, right: 0 }}
style={{ x, rotate }}
onDragEnd={handleDragEnd}
animate={controls}
whileTap={{ scale: 0.98, cursor: 'grabbing' }}
>
<div className="card-content">
<div className="card-header">
<span className="card-category">[{card.type}]</span>
</div>
<div className="card-body">
<p className="card-text">{card.text}</p>
</div>
</div>
{/* Swipe Intents */}
<motion.div className="swipe-intent left-intent" style={{ opacity: leftOpacity }}>
<span>{card.left?.text}</span>
</motion.div>
<motion.div className="swipe-intent right-intent" style={{ opacity: rightOpacity }}>
<span>{card.right?.text}</span>
</motion.div>
</motion.div>
</div>
);
}