Pular para o conteúdo
Projetos e Componentes

CARD

Código completo do componente para usar no seu projeto.

Publicado em: 09/2026 - Ativo
Ana Carolina

Ana Carolina

Product Designer

Focada em transformar problemas complexos em interfaces simples, acessíveis e bonitas de usar.

UX
UI
Design System
+24

Verifique se a stack tecnológica é compatível com o seu projeto e instale as dependências necessárias antes de utilizar o código.

Stack Tecnológica

Vue.js TypeScript Tailwind CSS Shadcn UI

Código do componente

<script setup lang="ts">
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card, CardContent } from '@/components/ui/card'
import { HeartIcon, Mail } from '@lucide/vue'

const member = {
  name: 'Ana Carolina',
  role: 'Product Designer',
  image: 'https://images.unsplash.com/photo-1580489944761-15a19d654956?w=500&q=80',
  bio: 'Focada em transformar problemas complexos em interfaces simples, acessíveis e bonitas de usar.',
  tags: ['UX', 'UI', 'Design System']
}

const tagPalette = [
  'text-pink-400 ring-1 ring-pink-400/20', // roxo
  'text-blue-400 ring-1 ring-blue-400/20', // lilás
  'text-orange-400 ring-1 ring-orange-400/20' // amarelo
]

const liked = ref(false)
const likesCount = ref(24)

interface FloatingHeart {
  id: number
  left: number
  duration: number
  delay: number
  drift: number
}

const floatingHearts = ref<FloatingHeart[]>([])
let nextHeartId = 0

function burstHearts(count: number) {
  for (let i = 0; i < count; i++) {
    const id = nextHeartId++
    const delay = i * 0.18 + Math.random() * 0.1
    floatingHearts.value.push({
      id,
      left: 15 + Math.random() * 70,
      duration: 1.3 + Math.random() * 0.7,
      delay,
      drift: (Math.random() - 0.5) * 70
    })
    setTimeout(() => {
      floatingHearts.value = floatingHearts.value.filter(heart => heart.id !== id)
    }, (delay + 2.2) * 1000)
  }
}

function toggleLike() {
  liked.value = !liked.value
  likesCount.value += liked.value ? 1 : -1
  if (liked.value) burstHearts(likesCount.value)
}
</script>

<template>
  <Card class="w-full flex-col gap-2 rounded-lg p-0 min-[700px]:flex-row">
    <div
      class="relative h-65 w-full overflow-hidden rounded-t-lg min-[700px]:h-auto min-[700px]:w-1/2 min-[700px]:min-w-[50%] min-[700px]:self-stretch min-[700px]:rounded-t-none min-[700px]:rounded-l-lg">
      <img :src="member.image" :alt="member.name" class="h-full w-full object-cover">

      <div class="pointer-events-none absolute inset-0">
        <HeartIcon v-for="heart in floatingHearts" :key="heart.id"
          class="floating-heart absolute size-6 fill-red-500 text-red-500" :style="{
            left: `${heart.left}%`,
            animationDuration: `${heart.duration}s`,
            animationDelay: `${heart.delay}s`,
            '--drift': `${heart.drift}px`,
          }" />
      </div>
    </div>

    <CardContent class="flex flex-col gap-4 px-6 py-6 min-[480px]:flex-1 min-[480px]:justify-center min-[700px]:pr-6">
      <div>
        <h2 class="text-2xl font-normal">
          {{ member.name }}
        </h2>
        <h3 class="text-lg font-normal text-muted-foreground">
          {{ member.role }}
        </h3>
      </div>

      <p class="truncate text-muted-foreground min-[560px]:text-wrap min-[560px]:whitespace-normal">
        {{ member.bio }}
      </p>

      <div
        class="flex flex-col gap-4 pb-4 min-[560px]:flex-row min-[560px]:items-center min-[700px]:flex-col min-[700px]:items-start">
        <div class="flex gap-2.5">
          <Badge v-for="(tag, index) in member.tags" :key="tag" variant="secondary"
            class="h-auto rounded-2xl px-3 py-1 text-sm font-normal italic backdrop-blur-lg"
            :class="tagPalette[index % tagPalette.length]">
            {{ tag }}
          </Badge>
        </div>

        <div class="flex gap-2 min-[560px]:ml-auto min-[700px]:mt-4 min-[700px]:ml-0">
          <Button
            class="h-12 w-full flex-1 rounded-full px-6 min-[560px]:w-fit min-[560px]:flex-none cursor-pointer border border-transparent transition-colors hover:border-foreground/70"
            variant="default">
            <Mail />
            Contato
          </Button>
          <div class="flex items-center gap-1.5">
            <Button size="icon" variant="secondary" :aria-pressed="liked" aria-label="Curtir"
              class="size-12 shrink-0 rounded-full bg-foreground/10 hover:bg-foreground/18 cursor-pointer"
              :class="liked && 'bg-red-900 text-white hover:bg-red-700'" @click="toggleLike">
              <HeartIcon :class="liked && 'fill-current'" />
            </Button>
            <span class="text-sm text-muted-foreground">+{{ likesCount }}</span>
          </div>
        </div>
      </div>
    </CardContent>
  </Card>
</template>

<style scoped>
.floating-heart {
  animation-name: float-up;
  animation-timing-function: ease-out;
  animation-fill-mode: both;
}

@keyframes float-up {
  0% {
    bottom: 1rem;
    transform: translateX(0) scale(0.5);
    opacity: 0;
  }

  15% {
    opacity: 1;
    transform: translateX(0) scale(1);
  }

  100% {
    bottom: 50%;
    transform: translateX(var(--drift)) scale(0.85);
    opacity: 0;
  }
}
</style>