246 lines
4.7 KiB
Vue
246 lines
4.7 KiB
Vue
<template>
|
|
<div
|
|
v-motion
|
|
:initial="animated ? animationConfig.initial : {}"
|
|
:enter="animated ? animationConfig.enter : {}"
|
|
:hovered="hoverable ? { scale: 1.02 } : {}"
|
|
:delay="delay"
|
|
class="glass-card"
|
|
:class="[
|
|
`glass-card--${variant}`,
|
|
`glass-card--${size}`,
|
|
{
|
|
'glass-card--clickable': clickable,
|
|
'glass-card--elevated': elevated
|
|
}
|
|
]"
|
|
>
|
|
<div v-if="hasHeader" class="glass-card__header">
|
|
<slot name="header">
|
|
<h3 v-if="title" class="glass-card__title">{{ title }}</h3>
|
|
<p v-if="subtitle" class="glass-card__subtitle">{{ subtitle }}</p>
|
|
</slot>
|
|
</div>
|
|
|
|
<div class="glass-card__body">
|
|
<slot />
|
|
</div>
|
|
|
|
<div v-if="hasFooter" class="glass-card__footer">
|
|
<slot name="footer" />
|
|
</div>
|
|
|
|
<div v-if="gradient" class="glass-card__gradient"></div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, useSlots } from 'vue'
|
|
|
|
interface Props {
|
|
title?: string
|
|
subtitle?: string
|
|
variant?: 'light' | 'dark' | 'colored' | 'gradient'
|
|
size?: 'sm' | 'md' | 'lg' | 'xl'
|
|
clickable?: boolean
|
|
hoverable?: boolean
|
|
elevated?: boolean
|
|
gradient?: boolean
|
|
animated?: boolean
|
|
delay?: number
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
variant: 'light',
|
|
size: 'md',
|
|
clickable: false,
|
|
hoverable: true,
|
|
elevated: true,
|
|
gradient: false,
|
|
animated: true,
|
|
delay: 0
|
|
})
|
|
|
|
const slots = useSlots()
|
|
const hasHeader = computed(() => !!slots.header || props.title || props.subtitle)
|
|
const hasFooter = computed(() => !!slots.footer)
|
|
|
|
const animationConfig = {
|
|
initial: {
|
|
opacity: 0,
|
|
y: 20,
|
|
scale: 0.95
|
|
},
|
|
enter: {
|
|
opacity: 1,
|
|
y: 0,
|
|
scale: 1,
|
|
transition: {
|
|
type: 'spring',
|
|
stiffness: 200,
|
|
damping: 20
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.glass-card {
|
|
position: relative;
|
|
border-radius: 20px;
|
|
overflow: hidden;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
|
// Variants - Updated to bolt.ai style with reduced blur
|
|
&--light {
|
|
background: rgba(255, 255, 255, 0.6);
|
|
backdrop-filter: blur(4px);
|
|
-webkit-backdrop-filter: blur(4px);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
color: #27272a;
|
|
}
|
|
|
|
&--dark {
|
|
background: rgba(0, 0, 0, 0.05);
|
|
backdrop-filter: blur(2px);
|
|
-webkit-backdrop-filter: blur(2px);
|
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
color: #27272a;
|
|
}
|
|
|
|
&--colored {
|
|
background: rgba(254, 242, 242, 0.6);
|
|
backdrop-filter: blur(2px);
|
|
-webkit-backdrop-filter: blur(2px);
|
|
border: 1px solid rgba(220, 38, 38, 0.1);
|
|
color: #27272a;
|
|
}
|
|
|
|
&--gradient {
|
|
background: linear-gradient(135deg, #fef2f2 0%, #ffffff 100%);
|
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
|
color: #27272a;
|
|
}
|
|
|
|
// Sizes
|
|
&--sm {
|
|
.glass-card__body {
|
|
padding: 1rem;
|
|
}
|
|
.glass-card__header {
|
|
padding: 1rem 1rem 0.5rem;
|
|
}
|
|
.glass-card__footer {
|
|
padding: 0.5rem 1rem 1rem;
|
|
}
|
|
}
|
|
|
|
&--md {
|
|
.glass-card__body {
|
|
padding: 1.5rem;
|
|
}
|
|
.glass-card__header {
|
|
padding: 1.5rem 1.5rem 0.75rem;
|
|
}
|
|
.glass-card__footer {
|
|
padding: 0.75rem 1.5rem 1.5rem;
|
|
}
|
|
}
|
|
|
|
&--lg {
|
|
.glass-card__body {
|
|
padding: 2rem;
|
|
}
|
|
.glass-card__header {
|
|
padding: 2rem 2rem 1rem;
|
|
}
|
|
.glass-card__footer {
|
|
padding: 1rem 2rem 2rem;
|
|
}
|
|
}
|
|
|
|
&--xl {
|
|
.glass-card__body {
|
|
padding: 2.5rem;
|
|
}
|
|
.glass-card__header {
|
|
padding: 2.5rem 2.5rem 1.25rem;
|
|
}
|
|
.glass-card__footer {
|
|
padding: 1.25rem 2.5rem 2.5rem;
|
|
}
|
|
}
|
|
|
|
// States
|
|
&--clickable {
|
|
cursor: pointer;
|
|
|
|
&:active {
|
|
transform: scale(0.98);
|
|
}
|
|
}
|
|
|
|
&--elevated {
|
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08);
|
|
|
|
&:hover {
|
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.12);
|
|
}
|
|
}
|
|
|
|
&:hover {
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
// Header
|
|
&__header {
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
&__title {
|
|
font-size: 1.25rem;
|
|
font-weight: 600;
|
|
margin: 0;
|
|
color: #27272a;
|
|
}
|
|
|
|
&__subtitle {
|
|
font-size: 0.875rem;
|
|
margin: 0.25rem 0 0;
|
|
opacity: 0.8;
|
|
}
|
|
|
|
// Body
|
|
&__body {
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
// Footer
|
|
&__footer {
|
|
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
// Gradient overlay
|
|
&__gradient {
|
|
position: absolute;
|
|
top: 0;
|
|
right: 0;
|
|
width: 50%;
|
|
height: 100%;
|
|
background: linear-gradient(90deg,
|
|
transparent 0%,
|
|
rgba(220, 38, 38, 0.05) 100%);
|
|
pointer-events: none;
|
|
}
|
|
}
|
|
|
|
// Dark mode support
|
|
@media (prefers-color-scheme: dark) {
|
|
.glass-card--light {
|
|
background: rgba(30, 30, 30, 0.7);
|
|
border-color: rgba(255, 255, 255, 0.1);
|
|
color: #ffffff;
|
|
}
|
|
}
|
|
</style> |