Browse Source
- Introduced ProductCard component to display individual product details, including image, title, description, price, and optional badges. - Added ProductGrid component to create a flexible grid layout for displaying multiple ProductCard components, supporting 1-4 columns. - Created a demo page to showcase the ProductCard and ProductGrid components in action, highlighting their responsive design and features. - Updated styleguide to include links and descriptions for the new components.main
4 changed files with 400 additions and 1 deletions
@ -0,0 +1,125 @@ |
|||
<script setup lang="ts"> |
|||
import { cn } from '~/lib/utils' |
|||
|
|||
interface Props { |
|||
/** |
|||
* Product image URL |
|||
*/ |
|||
image: string |
|||
/** |
|||
* Product name (e.g., "Makerspace Jahreskarte") |
|||
*/ |
|||
title: string |
|||
/** |
|||
* Short product description |
|||
*/ |
|||
description?: string |
|||
/** |
|||
* Price in EUR (will be formatted) |
|||
*/ |
|||
price: number |
|||
/** |
|||
* Optional badge text (e.g., "Beliebt", "Neu") |
|||
*/ |
|||
badge?: string |
|||
/** |
|||
* Optional discount percentage |
|||
*/ |
|||
discountPercentage?: number |
|||
/** |
|||
* Product ID for navigation |
|||
*/ |
|||
productId?: string |
|||
/** |
|||
* Additional CSS classes |
|||
*/ |
|||
class?: string |
|||
} |
|||
|
|||
const props = defineProps<Props>() |
|||
|
|||
// Format price in EUR |
|||
const formattedPrice = computed(() => { |
|||
return new Intl.NumberFormat('de-DE', { |
|||
style: 'currency', |
|||
currency: 'EUR', |
|||
}).format(props.price) |
|||
}) |
|||
</script> |
|||
|
|||
<template> |
|||
<div |
|||
:class=" |
|||
cn( |
|||
'group relative flex flex-col overflow-hidden rounded-2xl bg-white/10 backdrop-blur-lg border border-white/20 shadow-glass transition-all duration-300 hover:scale-[1.02] hover:shadow-2xl hover:border-white/30', |
|||
props.class, |
|||
) |
|||
" |
|||
> |
|||
<!-- Badge (optional) --> |
|||
<div |
|||
v-if="badge" |
|||
class="absolute left-4 top-4 z-10 rounded-full bg-experimenta-primary px-3 py-1 text-xs font-bold uppercase tracking-wider text-white shadow-lg" |
|||
> |
|||
{{ badge }} |
|||
</div> |
|||
|
|||
<!-- Discount Badge (optional) --> |
|||
<div |
|||
v-if="discountPercentage" |
|||
class="absolute right-4 top-4 z-10 flex h-12 w-12 items-center justify-center rounded-full bg-red text-white shadow-lg" |
|||
> |
|||
<span class="text-xs font-bold">-{{ discountPercentage }}%</span> |
|||
</div> |
|||
|
|||
<!-- Product Image --> |
|||
<div class="relative aspect-[16/9] w-full overflow-hidden bg-purple-dark"> |
|||
<img |
|||
:src="image" |
|||
:alt="title" |
|||
class="h-full w-full object-cover transition-transform duration-500 group-hover:scale-110" |
|||
/> |
|||
<!-- Gradient overlay for better text readability --> |
|||
<div |
|||
class="absolute inset-0 bg-gradient-to-t from-purple-darkest/80 via-transparent to-transparent" |
|||
/> |
|||
</div> |
|||
|
|||
<!-- Card Content --> |
|||
<div class="flex flex-1 flex-col space-y-3 p-5"> |
|||
<!-- Title --> |
|||
<h3 |
|||
class="text-xl font-bold leading-tight text-white transition-colors group-hover:text-experimenta-accent" |
|||
> |
|||
{{ title }} |
|||
</h3> |
|||
|
|||
<!-- Description --> |
|||
<p v-if="description" class="flex-1 text-sm leading-relaxed text-white/80"> |
|||
{{ description }} |
|||
</p> |
|||
|
|||
<!-- Price & CTA --> |
|||
<div class="flex items-center justify-between gap-3 pt-2"> |
|||
<!-- Price --> |
|||
<div class="flex flex-col"> |
|||
<span class="text-xs uppercase tracking-wide text-white/60">Preis</span> |
|||
<span class="text-2xl font-bold text-experimenta-accent"> |
|||
{{ formattedPrice }} |
|||
</span> |
|||
</div> |
|||
|
|||
<!-- CTA Button --> |
|||
<NuxtLink |
|||
:to="productId ? `/produkte/${productId}` : '#'" |
|||
class="group/btn relative overflow-hidden rounded-xl bg-gradient-button bg-size-300 bg-left px-6 py-3 font-bold text-white shadow-lg transition-all duration-300 hover:bg-right hover:shadow-2xl active:scale-95" |
|||
> |
|||
<span class="relative z-10">Details</span> |
|||
<div |
|||
class="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent opacity-0 transition-opacity duration-300 group-hover/btn:opacity-100" |
|||
/> |
|||
</NuxtLink> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
@ -0,0 +1,66 @@ |
|||
<script setup lang="ts"> |
|||
import { cn } from '~/lib/utils' |
|||
|
|||
interface Props { |
|||
/** |
|||
* Grid title (e.g., "Unsere Jahreskarten") |
|||
*/ |
|||
title?: string |
|||
/** |
|||
* Grid description/subtitle |
|||
*/ |
|||
description?: string |
|||
/** |
|||
* Number of columns on desktop (default: 3) |
|||
*/ |
|||
columns?: 1 | 2 | 3 | 4 |
|||
/** |
|||
* Additional CSS classes |
|||
*/ |
|||
class?: string |
|||
} |
|||
|
|||
const props = withDefaults(defineProps<Props>(), { |
|||
columns: 3, |
|||
}) |
|||
|
|||
// Generate grid column classes |
|||
const gridClass = computed(() => { |
|||
const colMap = { |
|||
1: 'md:grid-cols-1', |
|||
2: 'md:grid-cols-2', |
|||
3: 'md:grid-cols-3', |
|||
4: 'md:grid-cols-4', |
|||
} |
|||
return colMap[props.columns] |
|||
}) |
|||
</script> |
|||
|
|||
<template> |
|||
<section :class="cn('w-full', props.class)"> |
|||
<!-- Section Header --> |
|||
<div v-if="title || description" class="mb-8 text-center"> |
|||
<h2 |
|||
v-if="title" |
|||
class="mb-3 text-3xl font-bold text-white md:text-4xl" |
|||
> |
|||
{{ title }} |
|||
</h2> |
|||
<p v-if="description" class="mx-auto max-w-2xl text-base text-white/80 md:text-lg"> |
|||
{{ description }} |
|||
</p> |
|||
</div> |
|||
|
|||
<!-- Product Grid --> |
|||
<div |
|||
:class=" |
|||
cn( |
|||
'grid grid-cols-1 gap-6 sm:grid-cols-2', |
|||
gridClass, |
|||
) |
|||
" |
|||
> |
|||
<slot /> |
|||
</div> |
|||
</section> |
|||
</template> |
|||
@ -0,0 +1,191 @@ |
|||
<script setup lang="ts"> |
|||
// Demo page to showcase product cards |
|||
|
|||
definePageMeta({ |
|||
layout: 'styleguide', |
|||
}) |
|||
|
|||
// Sample product data |
|||
const products = [ |
|||
{ |
|||
id: 'makerspace-jk-2025', |
|||
image: '/img/makerspace-jk-2025.jpg', |
|||
title: 'Makerspace Jahreskarte', |
|||
description: |
|||
'Unbegrenzter Zugang zum Makerspace für 365 Tage. Nutze modernste Werkzeuge, 3D-Drucker und vieles mehr.', |
|||
price: 120.0, |
|||
badge: 'Beliebt', |
|||
}, |
|||
{ |
|||
id: 'experimenta-jk-2025', |
|||
image: '/img/makerspace-jk-2025.jpg', // Using same image as placeholder |
|||
title: 'experimenta Jahreskarte', |
|||
description: |
|||
'Erlebe die Ausstellungswelt der experimenta ein ganzes Jahr lang. Mit freiem Eintritt zu allen Ausstellungen.', |
|||
price: 85.0, |
|||
}, |
|||
{ |
|||
id: 'paedagogen-jk-2025', |
|||
image: '/img/makerspace-jk-2025.jpg', // Using same image as placeholder |
|||
title: 'Pädagogen Jahreskarte', |
|||
description: |
|||
'Speziell für Lehrkräfte und Pädagogen. Mit exklusiven Fortbildungsangeboten und Materialien.', |
|||
price: 60.0, |
|||
badge: 'Neu', |
|||
discountPercentage: 15, |
|||
}, |
|||
] |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="min-h-screen bg-gradient-primary px-4 py-12 md:px-6 lg:px-8"> |
|||
<!-- Page Header --> |
|||
<div class="mx-auto mb-12 max-w-container-wide text-center"> |
|||
<h1 class="mb-4 text-4xl font-bold text-white md:text-5xl"> |
|||
Produktkarten Demo |
|||
</h1> |
|||
<p class="mx-auto max-w-2xl text-lg text-white/80"> |
|||
Mobile-optimierte Produktkarten für Jahreskarten und andere Produkte der experimenta. |
|||
</p> |
|||
</div> |
|||
|
|||
<!-- Product Grid Section --> |
|||
<div class="mx-auto max-w-container-wide"> |
|||
<ProductGrid |
|||
title="Unsere Jahreskarten" |
|||
description="Wähle die passende Jahreskarte für deine Bedürfnisse. Alle Karten sind 365 Tage gültig und sofort einsetzbar." |
|||
:columns="3" |
|||
> |
|||
<ProductCard |
|||
v-for="product in products" |
|||
:key="product.id" |
|||
:image="product.image" |
|||
:title="product.title" |
|||
:description="product.description" |
|||
:price="product.price" |
|||
:badge="product.badge" |
|||
:discount-percentage="product.discountPercentage" |
|||
:product-id="product.id" |
|||
/> |
|||
</ProductGrid> |
|||
</div> |
|||
|
|||
<!-- Design Variants Section --> |
|||
<div class="mx-auto mt-20 max-w-container-wide"> |
|||
<h2 class="mb-8 text-center text-3xl font-bold text-white"> |
|||
Design-Varianten |
|||
</h2> |
|||
|
|||
<!-- Single Column Layout --> |
|||
<div class="mb-12"> |
|||
<h3 class="mb-4 text-xl font-bold text-white">Single Column (Mobile)</h3> |
|||
<ProductGrid :columns="1"> |
|||
<ProductCard |
|||
:image="products[0].image" |
|||
:title="products[0].title" |
|||
:description="products[0].description" |
|||
:price="products[0].price" |
|||
:badge="products[0].badge" |
|||
:product-id="products[0].id" |
|||
/> |
|||
</ProductGrid> |
|||
</div> |
|||
|
|||
<!-- Two Column Layout --> |
|||
<div class="mb-12"> |
|||
<h3 class="mb-4 text-xl font-bold text-white">Two Columns</h3> |
|||
<ProductGrid :columns="2"> |
|||
<ProductCard |
|||
v-for="product in products.slice(0, 2)" |
|||
:key="product.id" |
|||
:image="product.image" |
|||
:title="product.title" |
|||
:description="product.description" |
|||
:price="product.price" |
|||
:badge="product.badge" |
|||
:product-id="product.id" |
|||
/> |
|||
</ProductGrid> |
|||
</div> |
|||
|
|||
<!-- Four Column Layout (Desktop only) --> |
|||
<div class="mb-12"> |
|||
<h3 class="mb-4 text-xl font-bold text-white"> |
|||
Four Columns (Desktop - shows 2 cols on mobile) |
|||
</h3> |
|||
<ProductGrid :columns="4"> |
|||
<ProductCard |
|||
v-for="product in [...products, products[0]]" |
|||
:key="product.id + '-4col'" |
|||
:image="product.image" |
|||
:title="product.title" |
|||
:price="product.price" |
|||
:product-id="product.id" |
|||
/> |
|||
</ProductGrid> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Component Features --> |
|||
<div class="mx-auto mt-20 max-w-container-wide"> |
|||
<div class="rounded-2xl border border-white/20 bg-white/10 p-8 backdrop-blur-lg"> |
|||
<h2 class="mb-6 text-2xl font-bold text-white"> |
|||
Component Features |
|||
</h2> |
|||
<ul class="space-y-3 text-white/90"> |
|||
<li class="flex items-start gap-2"> |
|||
<span class="mt-1 text-experimenta-accent">✓</span> |
|||
<span><strong>Mobile-First:</strong> Optimiert für Smartphones, skaliert perfekt auf Desktop</span> |
|||
</li> |
|||
<li class="flex items-start gap-2"> |
|||
<span class="mt-1 text-experimenta-accent">✓</span> |
|||
<span><strong>Responsive Images:</strong> 16:9 Aspect Ratio mit Zoom-Effekt beim Hover</span> |
|||
</li> |
|||
<li class="flex items-start gap-2"> |
|||
<span class="mt-1 text-experimenta-accent">✓</span> |
|||
<span><strong>Optional Badges:</strong> "Beliebt", "Neu", oder eigene Texte</span> |
|||
</li> |
|||
<li class="flex items-start gap-2"> |
|||
<span class="mt-1 text-experimenta-accent">✓</span> |
|||
<span><strong>Discount Badge:</strong> Prozentuale Rabatte hervorheben</span> |
|||
</li> |
|||
<li class="flex items-start gap-2"> |
|||
<span class="mt-1 text-experimenta-accent">✓</span> |
|||
<span><strong>Gradient Button:</strong> Animierter Gradient beim Hover</span> |
|||
</li> |
|||
<li class="flex items-start gap-2"> |
|||
<span class="mt-1 text-experimenta-accent">✓</span> |
|||
<span><strong>Glassmorphism Design:</strong> Backdrop blur mit experimenta Farbpalette</span> |
|||
</li> |
|||
<li class="flex items-start gap-2"> |
|||
<span class="mt-1 text-experimenta-accent">✓</span> |
|||
<span><strong>Flexible Grid:</strong> 1-4 Spalten auf Desktop, automatisch responsive</span> |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Usage Example --> |
|||
<div class="mx-auto mt-12 max-w-container-wide"> |
|||
<div class="rounded-2xl border border-white/20 bg-purple-darker/50 p-8 backdrop-blur-lg"> |
|||
<h2 class="mb-4 text-2xl font-bold text-white"> |
|||
Usage Example |
|||
</h2> |
|||
<pre class="overflow-x-auto rounded-lg bg-purple-darkest p-4 text-sm text-white/90"><code><ProductGrid |
|||
title="Unsere Jahreskarten" |
|||
description="Wähle die passende Karte" |
|||
:columns="3" |
|||
> |
|||
<ProductCard |
|||
image="/img/makerspace-jk-2025.jpg" |
|||
title="Makerspace Jahreskarte" |
|||
description="Unbegrenzter Zugang..." |
|||
:price="120.00" |
|||
badge="Beliebt" |
|||
product-id="makerspace-jk-2025" |
|||
/> |
|||
</ProductGrid></code></pre> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
Loading…
Reference in new issue