This commit is contained in:
2026-05-16 00:20:34 +02:00
parent d55b0e2914
commit 378baf4458
13 changed files with 1815 additions and 1389 deletions
+91 -39
View File
@@ -8,61 +8,113 @@ import (
templ CategoryPage(data viewmodel.CategoryPageData, cssPath string, jsPath string) {
@Layout(data.Category.Name, cssPath, jsPath, data.Menu, data.Locale, layoutCartItems(data.CartSummary)) {
<main class="min-h-screen bg-[radial-gradient(circle_at_top_left,_rgba(34,197,94,0.18),_transparent_35%),linear-gradient(180deg,#0b1020,#111827)]">
<div class="mx-auto flex w-full max-w-[104rem] flex-col gap-10 px-6 py-10 lg:px-8">
<header class="rounded-[2rem] border border-emerald-500/20 bg-white/5 p-8 backdrop-blur">
<p class="text-xs uppercase tracking-[0.32em] text-emerald-300">Category</p>
<h1 class="mt-4 font-serif text-4xl text-white">{ data.Category.Name }</h1>
<div class="mt-4 flex items-center justify-between gap-6 text-sm text-slate-300">
<p>{ fmt.Sprintf("Products %d-%d of %d", categoryPageStart(data.Pagination), categoryPageEnd(data.Pagination, len(data.Category.Products)), data.Pagination.TotalItems) }</p>
<main class="min-h-screen bg-[#fdfbf7]">
<div class="site-container flex flex-col gap-8 py-6 sm:py-8 lg:py-10">
<nav class="rounded-sm bg-[#eceae7] px-4 py-3 text-[0.82rem] text-stone-500 sm:px-5">
<div class="flex flex-wrap items-center gap-x-2 gap-y-1">
<a class="transition hover:text-amber-600" href={ data.ShopBaseURL }>9b-plus</a>
<span>/</span>
<span>Category</span>
<span>/</span>
<span class="text-stone-700">{ data.Category.Name }</span>
</div>
</nav>
<header class="border-b border-stone-200 pb-6">
<div class="flex flex-col gap-5 lg:flex-row lg:items-start lg:justify-between">
<div class="max-w-3xl">
<p class="text-[0.72rem] font-semibold uppercase tracking-[0.26em] text-stone-400">Category</p>
<h1 class="mt-4 text-3xl font-medium uppercase tracking-[0.06em] text-stone-800 sm:text-[2.1rem]">{ data.Category.Name }</h1>
if data.Category.Description != "" {
<div class="category-description mt-5 max-w-2xl text-sm leading-7 text-stone-500">
@templ.Raw(data.Category.Description)
</div>
}
</div>
if data.Customer != nil {
<p>{ fmt.Sprintf("%s %s", data.Customer.FirstName, data.Customer.LastName) }</p>
} else {
<p>Guest session</p>
<div class="text-sm text-stone-500 lg:pt-8">
{ fmt.Sprintf("%s %s", data.Customer.FirstName, data.Customer.LastName) }
</div>
}
</div>
if data.Category.Description != "" {
<div class="prose prose-invert mt-6 max-w-none text-slate-300">
@templ.Raw(data.Category.Description)
</div>
}
</header>
<section class="grid gap-5 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
for _, product := range data.Category.Products {
<article class="group rounded-[1.75rem] border border-white/10 bg-slate-900/70 p-6 shadow-[0_24px_80px_rgba(0,0,0,0.35)] transition hover:-translate-y-1 hover:border-emerald-400/40">
if product.ImageURL != "" {
<a class="mb-5 block overflow-hidden rounded-[1.25rem] border border-white/10 bg-slate-950/70 shadow-[0_16px_40px_rgba(0,0,0,0.24)]" href={ product.URL }>
<img class="block h-64 w-full object-cover transition duration-500 group-hover:scale-[1.03]" src={ product.ImageURL } alt={ product.Name }/>
<section class="flex flex-col gap-4 border-y border-stone-200 py-4">
<div class="flex flex-col gap-4 xl:flex-row xl:items-center xl:justify-between">
<div class="flex items-center gap-4">
<button class="inline-flex h-11 w-11 items-center justify-center bg-amber-500 text-xl text-white shadow-[0_8px_18px_rgba(245,158,11,0.28)] transition hover:bg-amber-600" type="button" aria-label="Filters">
</button>
<p class="text-sm text-stone-500">
{ fmt.Sprintf("Showing %d-%d of %d products", categoryPageStart(data.Pagination), categoryPageEnd(data.Pagination, len(data.Category.Products)), data.Pagination.TotalItems) }
</p>
</div>
<div class="flex flex-wrap items-center gap-4 sm:justify-end">
if data.Category.Description != "" {
<a class="inline-flex min-h-11 items-center justify-center bg-amber-500 px-6 text-[0.72rem] font-semibold uppercase tracking-[0.22em] text-white shadow-[0_8px_18px_rgba(245,158,11,0.22)] transition hover:bg-amber-600" href="#category-description">
More
</a>
}
<p class="text-xs uppercase tracking-[0.28em] text-emerald-300">Product</p>
<h2 class="mt-3 text-2xl font-semibold text-white">{ product.Name }</h2>
<p class="mt-4 text-sm leading-7 text-slate-300">{ truncatedPlainTextHTML(product.ShortDescription, 220) }</p>
<div class="mt-8 flex items-center justify-between gap-4">
<div>
<p class="text-2xl font-semibold text-white">{ moneyWithCurrency(product.PriceTaxIncl, product.CurrencySign, product.CurrencyCode) }</p>
<p class="mt-1 text-xs uppercase tracking-[0.2em] text-slate-400">{ taxLabel(product.TaxRate) } · { conversionRateLabel(product.ConversionRate, product.CurrencyCode) }</p>
<label class="flex min-w-[13rem] flex-col gap-1 text-[0.64rem] font-semibold uppercase tracking-[0.18em] text-stone-400">
<span>Sort by</span>
<select class="border-0 border-b border-amber-300 bg-transparent px-0 py-1 text-sm font-medium normal-case tracking-normal text-stone-700 focus:border-amber-500 focus:outline-none focus:ring-0">
<option>Most popular</option>
<option>Price: low to high</option>
<option>Price: high to low</option>
</select>
</label>
</div>
</div>
</section>
<section class="grid gap-x-6 gap-y-12 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5">
for _, product := range data.Category.Products {
<article class="group flex h-full flex-col items-center text-center">
<a class="flex h-full w-full flex-col items-center rounded-sm px-3 pb-4 pt-2 transition hover:-translate-y-1" href={ product.URL }>
if product.ImageURL != "" {
<div class="flex h-[20rem] w-full items-center justify-center overflow-hidden bg-white">
<img class="max-h-[15.5rem] w-auto max-w-[82%] object-contain transition duration-500 group-hover:scale-[1.04]" src={ product.ImageURL } alt={ product.Name }/>
</div>
} else {
<div class="flex h-[20rem] w-full items-center justify-center bg-stone-100 text-5xl font-semibold text-stone-300">
{ productInitial(product.Name) }
</div>
}
<h2 class="mt-6 text-[1.02rem] font-medium leading-6 text-stone-800">{ product.Name }</h2>
if product.ShortDescription != "" {
<p class="mt-3 max-w-[17rem] text-[0.92rem] leading-6 text-stone-400">{ truncatedPlainTextHTML(product.ShortDescription, 90) }</p>
} else if product.EAN13 != "" {
<p class="mt-3 text-[0.92rem] leading-6 text-stone-400">EAN { product.EAN13 }</p>
} else {
<p class="mt-3 text-[0.92rem] leading-6 text-stone-400">{ taxLabel(product.TaxRate) }</p>
}
<div class="mt-5 flex items-baseline gap-2 text-stone-900">
<p class="text-[2rem] font-semibold leading-none">{ moneyWithCurrency(product.PriceTaxIncl, product.CurrencySign, product.CurrencyCode) }</p>
</div>
<a class="rounded-full border border-emerald-400/40 px-4 py-2 text-xs font-semibold uppercase tracking-[0.22em] text-emerald-200 transition hover:bg-emerald-300 hover:text-slate-950" href={ product.URL }>
View Product
</a>
</div>
<p class="mt-3 text-[0.72rem] uppercase tracking-[0.18em] text-stone-400">{ conversionRateLabel(product.ConversionRate, product.CurrencyCode) }</p>
</a>
</article>
}
</section>
if data.Pagination.TotalPages > 1 {
<nav class="flex flex-col items-center justify-between gap-4 rounded-[1.75rem] border border-white/10 bg-slate-950/50 px-6 py-5 text-sm text-slate-300 md:flex-row">
<div>
<p class="text-xs uppercase tracking-[0.24em] text-emerald-300">Page</p>
<p class="mt-1">{ fmt.Sprintf("%d of %d", data.Pagination.Page, data.Pagination.TotalPages) }</p>
if data.Category.Description != "" {
<section class="rounded-sm border border-stone-200 bg-white px-5 py-6 shadow-[0_12px_30px_rgba(20,33,61,0.05)]" id="category-description">
<p class="text-[0.7rem] font-semibold uppercase tracking-[0.22em] text-stone-400">More about this category</p>
<div class="category-description mt-4 max-w-none text-sm leading-7 text-stone-600">
@templ.Raw(data.Category.Description)
</div>
</section>
}
if data.Pagination.TotalPages > 1 {
<nav class="flex flex-col items-center justify-between gap-4 border-t border-stone-200 pt-6 text-sm text-stone-500 md:flex-row">
<p>{ fmt.Sprintf("Page %d of %d", data.Pagination.Page, data.Pagination.TotalPages) }</p>
<div class="flex items-center gap-3">
if data.Pagination.PrevURL != "" {
<a class="rounded-full border border-white/10 px-4 py-2 font-semibold uppercase tracking-[0.2em] text-slate-200 transition hover:border-emerald-400/40 hover:text-white" href={ data.Pagination.PrevURL }>Previous</a>
<a class="inline-flex min-h-11 items-center justify-center border border-stone-300 px-5 font-semibold uppercase tracking-[0.18em] text-stone-700 transition hover:border-amber-500 hover:text-amber-600" href={ data.Pagination.PrevURL }>Previous</a>
}
if data.Pagination.NextURL != "" {
<a class="rounded-full border border-emerald-400/40 px-4 py-2 font-semibold uppercase tracking-[0.2em] text-emerald-200 transition hover:bg-emerald-300 hover:text-slate-950" href={ data.Pagination.NextURL }>Next</a>
<a class="inline-flex min-h-11 items-center justify-center bg-amber-500 px-5 font-semibold uppercase tracking-[0.18em] text-white transition hover:bg-amber-600" href={ data.Pagination.NextURL }>Next</a>
}
</div>
</nav>