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