172 lines
4.1 KiB
Svelte
172 lines
4.1 KiB
Svelte
<script lang="ts">
|
|
import { animate, spring } from "motion";
|
|
import { onMount, createEventDispatcher } from 'svelte';
|
|
import type { Profile } from "./profiles";
|
|
|
|
export let profile: Profile;
|
|
export let active = false;
|
|
|
|
let element: HTMLDivElement;
|
|
let x = 0;
|
|
let y = 0;
|
|
let isDragging = false;
|
|
let startX = 0;
|
|
let startY = 0;
|
|
let rotate = 0;
|
|
let opacity = 1;
|
|
let overlayColor = '';
|
|
|
|
const dispatch = createEventDispatcher<{
|
|
swipe: boolean;
|
|
}>();
|
|
|
|
onMount(() => {
|
|
if (active) {
|
|
setupDrag();
|
|
}
|
|
|
|
// Type the animation parameters properly
|
|
animate(
|
|
element,
|
|
{ scale: [0.9, 1] },
|
|
{ duration: 0.3, easing: 'ease-out' }
|
|
);
|
|
|
|
return () => {
|
|
// Cleanup listeners on component destroy
|
|
if (active) {
|
|
element?.removeEventListener('pointerdown', startDrag);
|
|
document.removeEventListener('pointerup', endDrag);
|
|
document.removeEventListener('pointermove', updateDrag);
|
|
}
|
|
};
|
|
});
|
|
|
|
function setupDrag() {
|
|
element?.addEventListener('pointerdown', startDrag);
|
|
document.addEventListener('pointerup', endDrag);
|
|
document.addEventListener('pointermove', updateDrag);
|
|
}
|
|
|
|
function startDrag(e: PointerEvent) {
|
|
if (!element) return;
|
|
isDragging = true;
|
|
startX = e.clientX - x;
|
|
startY = e.clientY - y;
|
|
element.style.cursor = 'grabbing';
|
|
}
|
|
|
|
function updateDrag(e: PointerEvent) {
|
|
if (!isDragging || !element) return;
|
|
|
|
x = e.clientX - startX;
|
|
y = e.clientY - startY;
|
|
rotate = x * 0.1;
|
|
opacity = Math.max(0.2, 1 - Math.abs(x) / 300);
|
|
|
|
overlayColor = x > 0
|
|
? `linear-gradient(90deg, rgba(34,197,94,0.2) 0%, rgba(255,255,255,0) 60%)`
|
|
: `linear-gradient(270deg, rgba(239,68,68,0.2) 0%, rgba(255,255,255,0) 60%)`;
|
|
|
|
updateElementTransform();
|
|
}
|
|
|
|
function updateElementTransform() {
|
|
if (!element) return;
|
|
element.style.transform = `translate(${x}px, ${y}px) rotate(${rotate}deg)`;
|
|
element.style.opacity = opacity.toString();
|
|
}
|
|
|
|
function endDrag() {
|
|
if (!isDragging || !element) return;
|
|
|
|
isDragging = false;
|
|
element.style.cursor = 'grab';
|
|
|
|
const threshold = 100;
|
|
if (Math.abs(x) > threshold) {
|
|
swipe(x > 0);
|
|
} else {
|
|
resetCardPosition();
|
|
}
|
|
}
|
|
|
|
function resetCardPosition() {
|
|
animate(
|
|
element,
|
|
{
|
|
x: 0,
|
|
y: 0,
|
|
rotate: 0,
|
|
opacity: 1
|
|
} as any,
|
|
{
|
|
easing: spring({
|
|
stiffness: 300,
|
|
damping: 20
|
|
} as any)
|
|
} as any
|
|
);
|
|
overlayColor = '';
|
|
}
|
|
|
|
function swipe(right: boolean) {
|
|
const direction = right ? 500 : -500;
|
|
|
|
animate(
|
|
element,
|
|
{
|
|
x: direction,
|
|
y: y + (Math.random() - 0.5) * 100,
|
|
rotate: direction / 4,
|
|
opacity: 0,
|
|
scale: 0.8
|
|
},
|
|
{
|
|
easing: spring(5),
|
|
onComplete: () => {
|
|
x = 0;
|
|
y = 0;
|
|
rotate = 0;
|
|
opacity = 1;
|
|
overlayColor = '';
|
|
dispatch('swipe', right);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
</script>
|
|
|
|
<div
|
|
bind:this={element}
|
|
class="card w-96 bg-base-100 shadow-2xl absolute cursor-grab
|
|
transition-transform duration-200 hover:shadow-primary/20
|
|
hover:-translate-y-1"
|
|
style="transform: translate({x}px, {y}px) rotate({rotate}deg);
|
|
opacity: {opacity}"
|
|
>
|
|
<div
|
|
class="absolute inset-0 rounded-xl"
|
|
style="background: {overlayColor}"
|
|
></div>
|
|
<figure class="h-64 overflow-hidden">
|
|
<img
|
|
src={profile.img}
|
|
alt={profile.name}
|
|
class="w-full h-full object-cover transition-transform
|
|
duration-300 group-hover:scale-105"
|
|
/>
|
|
</figure>
|
|
<div class="card-body bg-gradient-to-t from-base-100
|
|
via-base-100/90 to-transparent">
|
|
<div class="flex items-center gap-2">
|
|
<h2 class="card-title text-3xl font-bold drop-shadow-sm">
|
|
{profile.name}
|
|
</h2>
|
|
<div class="badge badge-primary badge-lg p-4">
|
|
{profile.age}
|
|
</div>
|
|
</div>
|
|
<p class="text-lg text-base-content/80">{profile.bio}</p>
|
|
</div>
|
|
</div> |