16 Commits

Author SHA1 Message Date
Priec
5001e46866 button text
Some checks failed
CI / Check Style (push) Has been cancelled
CI / Run Clippy (push) Has been cancelled
CI / Run Tests (push) Has been cancelled
2026-06-26 23:03:38 +02:00
Priec
5dcc8028b2 COLOR CHANGE - buttons have logo color but faded 2026-06-26 22:28:45 +02:00
Priec
3df88b4cee color scheme matching the company 2026-06-26 20:39:14 +02:00
Priec
ba02930454 width of the cards is the same now 2026-06-26 20:30:34 +02:00
Priec
1fc8796389 better mobile3 - product cards 2026-06-26 15:48:04 +02:00
Priec
e5ec2a2de6 better mobile2 2026-06-26 12:59:14 +02:00
Priec
70908cba8b mobile view logo 2026-06-26 12:40:10 +02:00
Priec
6b3739d629 mobile view 2026-06-26 12:32:20 +02:00
Priec
f3b920d4b2 storing vertical vs horizontal product card 2026-06-25 23:22:53 +02:00
Priec
caec8b4fb3 better product card defaults 2026-06-25 23:20:49 +02:00
Priec
d6d4f19010 less height for the product card 2026-06-25 23:16:52 +02:00
Priec
77066d660c removed old page 2026-06-25 23:12:23 +02:00
Priec
3aa5f63264 contanct page 2026-06-25 23:07:40 +02:00
Priec
f04691a733 where and who we are 2026-06-25 22:35:35 +02:00
Priec
6dd1164c65 search now fixed and also the elements of the old site are back 2026-06-25 22:27:19 +02:00
Priec
5f7ddce6a7 menus in the same height 2026-06-25 21:42:21 +02:00
19 changed files with 293 additions and 315 deletions

View File

@@ -39,17 +39,24 @@
@theme {
/* light mode — Catppuccin Latte (https://catppuccin.com/palette)
* Base #eff1f5, Mantle #e6e9ef, Surface1 #bcc0cc, Subtext1 #5c5f77,
* Subtext0 #6c6f85, Text #4c4f69, Blue #1e66f5. */
* Subtext0 #6c6f85, Text #4c4f69. Primary is the KOMPRESS logo blue
* (sampled from logo.jpg) rather than the Latte blue. */
--color-surface: #eff1f5; /* Base */
--color-surface-alt: #e6e9ef; /* Mantle */
--color-on-surface: #5c5f77; /* Subtext1 */
--color-on-surface-strong: #4c4f69; /* Text */
--color-primary: #1e66f5; /* Blue */
--color-primary: #1600ff; /* KOMPRESS logo blue */
--color-on-primary: #eff1f5; /* Base */
--color-secondary: #6c6f85; /* Subtext0 */
--color-on-secondary: #eff1f5; /* Base */
--color-outline: #bcc0cc; /* Surface1 */
--color-outline-strong: #4c4f69; /* Text */
/* CTA: solid fill for large/filled buttons + the contact block. The vivid
* logo blue (--color-primary) is reserved for tiny accents (links, hover
* tints, badges); the CTA color is the logo blue itself, just with alpha so
* big buttons read as a translucent tint rather than the full vivid fill. */
--color-cta: rgba(22, 0, 255, 0.85);
--color-on-cta: #eff1f5;
/* dark mode — Gruvbox dark palette (https://github.com/morhetz/gruvbox)
* bg0 #282828, bg1 #3c3836, bg2 #504945, fg0 #fbf1c7, fg1 #ebdbb2,
@@ -64,6 +71,9 @@
--color-on-secondary-dark: #1d2021; /* bg0_h */
--color-outline-dark: #504945; /* bg2 */
--color-outline-dark-strong: #bdae93; /* fg3 */
/* CTA in dark mode tracks the existing primary so dark buttons are unchanged. */
--color-cta-dark: #83a598; /* = primary-dark */
--color-on-cta-dark: #1d2021; /* = on-primary-dark */
/* shared status colors (same in both modes) */
--color-info: var(--color-sky-500);

View File

@@ -1,6 +1,6 @@
brand = Kompress eshop
brand = WWW.KOMPRESS.SK, s.r.o.
hello-world = Hello world!
meta-description = Kompress eshop
meta-description = Manufacturer and distributor of medical aids and supplies
nav-home = Home
nav-about = About
nav-blog = Blog

View File

@@ -1,6 +1,6 @@
brand = Kompress eshop
brand = WWW.KOMPRESS.SK, s.r.o.
hello-world = Hello world!
meta-description = Kompress eshop
meta-description = Manufacturer and distributor of medical aids and supplies
nav-home = Home
nav-about = About
nav-blog = Blog
@@ -218,7 +218,7 @@ option-label = Option label
optional = optional
stock-untracked-hint = Leave blank = available without stock tracking
available = Available
choose-option = Choose an option
choose-option = Options
from-price = from { $price }
admin-discounts = Discounts
admin-discounts-desc = Set discounted product prices. A discount shows up as a sale in the shop.
@@ -300,7 +300,7 @@ position-hint = Sort order in the menu (lowest first). Leave blank to add it las
parent-category = Parent category
no-parent = — None (top level) —
quantity = Quantity
add-to-cart = Add to cart
add-to-cart = To cart
cart-added = Added to cart
in-stock = In stock
out-of-stock = Out of stock
@@ -523,7 +523,21 @@ footer-account = Account
footer-contact = Contact
footer-terms = Terms and conditions
footer-about = About our company
footer-stores = Our stores
footer-stores = Where it's made
home-stores-photo = Our production facility
home-stores-discover = Step inside our workshop
page-stores-intro = This is our own facility where our medical aids and supplies are produced.
page-stores-facility = Production facility
page-stores-address-label = Facility address
page-stores-address = Nádražná 328/62, 015 01 Rajec nad Rajčankou
page-stores-photo-caption = Our production facility in Rajec nad Rajčankou
page-stores-map = Where to find us
page-stores-map-open = Open in Google Maps
home-bestsellers = Best sellers
home-bestsellers-all = All best sellers
home-contact-title = Contact us
home-contact-text = Our hotline is available 24/7. We're happy to help you choose.
home-contact-cta = Contact the hotline
footer-shipping = Shipping and payment
footer-orders = My orders
footer-email = info@kompress.sk

View File

@@ -1,6 +1,6 @@
brand = Kompress eshop
brand = WWW.KOMPRESS.SK, s.r.o.
hello-world = Ahoj svet!
meta-description = Kompress eshop
meta-description = Výrobca a distribútor zdravotníckych pomôcok a potrieb
nav-home = Domov
nav-about = O mne
nav-blog = Blog
@@ -218,7 +218,7 @@ option-label = Označenie možnosti
optional = voliteľné
stock-untracked-hint = Nechajte prázdne = dostupné bez sledovania zásob
available = Dostupné
choose-option = Vyberte možnosť
choose-option = Options
from-price = od { $price }
admin-discounts = Zľavy
admin-discounts-desc = Nastavte zľavnené ceny produktov. Zľava sa v obchode zobrazí ako akcia.
@@ -300,7 +300,7 @@ position-hint = Poradie v menu (najnižšie ako prvé). Nechajte prázdne a prid
parent-category = Nadradená kategória
no-parent = — Žiadna (najvyššia úroveň) —
quantity = Množstvo
add-to-cart = Pridať do košíka
add-to-cart = Do košíka
cart-added = Pridané do košíka
in-stock = Na sklade
out-of-stock = Vypredané
@@ -523,7 +523,21 @@ footer-account = Účet
footer-contact = Kontakt
footer-terms = Obchodné podmienky
footer-about = O našej spoločnosti
footer-stores = Naše obchody
footer-stores = Kde to vzniká
home-stores-photo = Naša výrobná prevádzka
home-stores-discover = Nahliadnite do výroby
page-stores-intro = Toto je naša vlastná prevádzka, kde vyrábame naše zdravotnícke pomôcky a potreby.
page-stores-facility = Výrobná prevádzka
page-stores-address-label = Adresa prevádzky
page-stores-address = Nádražná 328/62, 015 01 Rajec nad Rajčankou
page-stores-photo-caption = Naša výrobná prevádzka v Rajci nad Rajčankou
page-stores-map = Kde nás nájdete
page-stores-map-open = Otvoriť v Google Mapách
home-bestsellers = Najpredávanejšie
home-bestsellers-all = Všetko najpredávanejšie
home-contact-title = Kontaktujte nás
home-contact-text = Naša horúca linka je dostupná 24/7. Radi vám poradíme s výberom.
home-contact-cta = Kontaktujte hotline
footer-shipping = Doprava a platba
footer-orders = Moje objednávky
footer-email = info@kompress.sk

File diff suppressed because one or more lines are too long

BIN
assets/static/img/logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

BIN
assets/static/img/store.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -92,20 +92,15 @@
<header
class="sticky top-0 z-30 border-b border-outline bg-surface/95 backdrop-blur dark:border-outline-dark dark:bg-surface-dark/95">
<nav x-data="{ mobile: false }" class="mx-auto flex max-w-7xl items-center gap-3 px-4 py-3 sm:gap-4">
<nav class="mx-auto flex max-w-7xl items-center gap-3 px-4 py-3 sm:gap-4">
<!-- category sidebar toggle (mobile only) -->
{% set hamburger_icon = ui::icon(name="hamburger", size="size-6") %}
{{ ui::icon_button(aria_label=t(key='categories', lang=lang | default(value='sk')), attrs='@click="cats = !cats" :aria-expanded="cats"', extra="lg:hidden", icon=hamburger_icon) }}
<!-- logo lockup: blue cross tile + wordmark + medical-supplies subtitle -->
<a href="/" class="flex shrink-0 items-center gap-2.5">
<span class="inline-flex size-9 items-center justify-center rounded-radius bg-primary text-on-primary dark:bg-primary-dark dark:text-on-primary-dark">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><rect x="10" y="3" width="4" height="18" rx="1.5"></rect><rect x="3" y="10" width="18" height="4" rx="1.5"></rect></svg>
</span>
<span class="flex flex-col leading-none">
<span class="text-lg font-extrabold tracking-tight text-primary dark:text-primary-dark">{{ t(key="brand", lang=lang | default(value='sk')) }}</span>
<span class="hidden text-[10px] font-semibold uppercase tracking-wider text-on-surface/50 sm:block dark:text-on-surface-dark/50">{{ t(key="brand-subtitle", lang=lang | default(value='sk')) }}</span>
</span>
<!-- real KOMPRESS logo from www.e-shop.kompress.sk (hidden on mobile;
the category drawer carries navigation there) -->
<a href="/" class="hidden shrink-0 items-center sm:flex">
<img src="/static/img/logo.jpg" alt="{{ t(key='brand', lang=lang | default(value='sk')) }}" width="260" height="52" class="h-8 w-auto dark:rounded-radius dark:bg-white dark:px-1.5 dark:py-0.5" />
</a>
<!-- in-header search → existing GET /search (q param). Only on the home
@@ -119,7 +114,7 @@
placeholder="{{ t(key='search-placeholder', lang=lang | default(value='sk')) }}"
aria-label="{{ t(key='search-placeholder', lang=lang | default(value='sk')) }}"
class="min-w-0 flex-1 border-0 bg-surface-alt px-2.5 py-2.5 text-sm text-on-surface placeholder:text-on-surface/50 focus:outline-none dark:bg-surface-dark-alt dark:text-on-surface-dark dark:placeholder:text-on-surface-dark/50" />
<button type="submit" class="shrink-0 bg-primary px-5 text-sm font-bold text-on-primary transition hover:opacity-90 dark:bg-primary-dark dark:text-on-primary-dark">{{ t(key="search-button", lang=lang | default(value='sk')) }}</button>
<button type="submit" class="shrink-0 bg-cta px-5 text-sm font-bold text-on-cta transition hover:opacity-90 dark:bg-cta-dark dark:text-on-cta-dark">{{ t(key="search-button", lang=lang | default(value='sk')) }}</button>
</div>
</form>
{% endif %}
@@ -140,7 +135,7 @@
<!-- account area: admin quick links / customer profile dropdown /
guest two-line "Vitajte · Prihláste sa" button (Kompress design) -->
{% if logged_in_admin %}
<div class="hidden items-center gap-3 sm:flex">
<div class="flex items-center gap-3">
{{ ui::nav_link(label=t(key="admin-title", lang=lang | default(value='sk')), href="/admin/dashboard", data_nav="/admin", variant="warning", attrs='hx-boost="false"') }}
<form method="post" action="/logout" hx-boost="false">
{{ ui::csrf_field() }}
@@ -150,9 +145,9 @@
{% elif logged_in_customer %}
{% include "partials/profile_menu.html" %}
{% else %}
<a href="/login" data-nav="/login" class="hidden items-center gap-2.5 rounded-radius px-2.5 py-1.5 text-on-surface transition hover:bg-surface-alt sm:inline-flex dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">
<a href="/login" data-nav="/login" class="inline-flex items-center gap-2.5 rounded-radius px-2.5 py-1.5 text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" class="size-5 shrink-0" aria-hidden="true"><circle cx="12" cy="8" r="4"></circle><path d="M5 20a7 7 0 0 1 14 0"></path></svg>
<span class="flex flex-col items-start leading-tight">
<span class="hidden flex-col items-start leading-tight sm:flex">
<span class="text-[11px] text-on-surface/50 dark:text-on-surface-dark/50">{{ t(key="welcome", lang=lang | default(value='sk')) }}</span>
<span class="text-sm font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="nav-login", lang=lang | default(value='sk')) }}</span>
</span>
@@ -199,54 +194,7 @@
<!-- settings (language + theme) dropdown (self-contained Alpine state) -->
{% include "partials/settings_dropdown.html" %}
<!-- mobile hamburger — Penguin animated icon swap (bars ↔ X), kept in
our ghost-square icon-button shell for consistency with cart/gear -->
<button type="button" @click="mobile = !mobile" :aria-expanded="mobile" aria-label="{{ t(key='menu', lang=lang | default(value='sk')) }}"
class="inline-flex size-9 shrink-0 items-center justify-center rounded-radius bg-transparent text-secondary transition hover:opacity-75 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-secondary active:opacity-100 active:outline-offset-0 md:hidden dark:text-secondary-dark dark:focus-visible:outline-secondary-dark">
{{ ui::icon(name="hamburger", size="size-6", attrs='x-show="!mobile"') }}
{{ ui::icon(name="close", size="size-6", attrs='x-cloak x-show="mobile"') }}
</button>
</div>
<!-- mobile menu panel — Penguin sidebar-style menu rows (hover:bg-primary/5,
underline focus), active state via data-nav + markActiveNav() -->
<ul x-show="mobile" x-cloak @click.outside="mobile = false" x-transition
class="absolute inset-x-0 top-full mx-4 mt-2 flex flex-col gap-1 rounded-radius border border-outline bg-surface p-2 shadow-lg md:hidden dark:border-outline-dark dark:bg-surface-dark-alt">
{% if on_home | default(value=false) %}
<li class="mb-1">
<form action="/search" method="get" role="search" class="flex overflow-hidden rounded-radius border border-outline dark:border-outline-dark">
<input type="search" name="q" autocomplete="off"
placeholder="{{ t(key='search-placeholder', lang=lang | default(value='sk')) }}"
aria-label="{{ t(key='search-placeholder', lang=lang | default(value='sk')) }}"
class="min-w-0 flex-1 border-0 bg-surface-alt px-3 py-2 text-sm text-on-surface placeholder:text-on-surface/50 focus:outline-none dark:bg-surface-dark-alt dark:text-on-surface-dark dark:placeholder:text-on-surface-dark/50" />
<button type="submit" class="shrink-0 bg-primary px-4 text-sm font-bold text-on-primary dark:bg-primary-dark dark:text-on-primary-dark">{{ t(key="search-button", lang=lang | default(value='sk')) }}</button>
</form>
</li>
{% endif %}
<li><a href="/" data-nav="/" class="block rounded-radius px-3 py-2 text-sm font-medium text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-primary focus:outline-hidden focus-visible:underline aria-[current=page]:font-semibold aria-[current=page]:bg-primary/10 aria-[current=page]:text-primary dark:text-on-surface-dark dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark">{{ t(key="nav-home", lang=lang | default(value='sk')) }}</a></li>
<li><a href="/shop" data-nav="/shop" class="block rounded-radius px-3 py-2 text-sm font-medium text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-primary focus:outline-hidden focus-visible:underline aria-[current=page]:font-semibold aria-[current=page]:bg-primary/10 aria-[current=page]:text-primary dark:text-on-surface-dark dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark">{{ t(key="nav-shop", lang=lang | default(value='sk')) }}</a></li>
{% if logged_in_admin %}
<li><a href="/admin/dashboard" hx-boost="false" data-nav="/admin" class="block rounded-radius px-3 py-2 text-sm font-medium text-warning underline-offset-2 transition hover:bg-primary/5 focus:outline-hidden focus-visible:underline">{{ t(key="admin-title", lang=lang | default(value='sk')) }}</a></li>
<li>
<form method="post" action="/logout" hx-boost="false">
{{ ui::csrf_field() }}
<button type="submit" class="block w-full rounded-radius px-3 py-2 text-left text-sm font-medium text-danger underline-offset-2 transition hover:bg-primary/5 focus:outline-hidden focus-visible:underline">{{ t(key="logout", lang=lang | default(value='sk')) }}</button>
</form>
</li>
{% elif logged_in_customer %}
<li><a href="/account/profile" data-nav="/account" class="block rounded-radius px-3 py-2 text-sm font-medium text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-primary focus:outline-hidden focus-visible:underline aria-[current=page]:font-semibold aria-[current=page]:bg-primary/10 aria-[current=page]:text-primary dark:text-on-surface-dark dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark">{{ t(key="nav-profile", lang=lang | default(value='sk')) }}</a></li>
<li>
<form method="post" action="/logout" hx-boost="false">
{{ ui::csrf_field() }}
<button type="submit" class="block w-full rounded-radius px-3 py-2 text-left text-sm font-medium text-danger underline-offset-2 transition hover:bg-primary/5 focus:outline-hidden focus-visible:underline">{{ t(key="logout", lang=lang | default(value='sk')) }}</button>
</form>
</li>
{% else %}
<li><a href="/login" data-nav="/login" class="block rounded-radius px-3 py-2 text-sm font-medium text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-primary focus:outline-hidden focus-visible:underline aria-[current=page]:font-semibold aria-[current=page]:bg-primary/10 aria-[current=page]:text-primary dark:text-on-surface-dark dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark">{{ t(key="nav-login", lang=lang | default(value='sk')) }}</a></li>
<li><a href="/register" data-nav="/register" class="block rounded-radius px-3 py-2 text-sm font-medium text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-primary focus:outline-hidden focus-visible:underline aria-[current=page]:font-semibold aria-[current=page]:bg-primary/10 aria-[current=page]:text-primary dark:text-on-surface-dark dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark">{{ t(key="nav-register", lang=lang | default(value='sk')) }}</a></li>
{% endif %}
</ul>
</nav>
</header>

View File

@@ -12,15 +12,45 @@
{% endblock breadcrumbs %}
{% block content %}
{% set L = lang | default(value='sk') %}
{# Home layout adapted from the Kompress design mockup: the left "Kategórie"
column is already supplied by base.html's #category-sidebar, so the main
area is split into a featured product grid + a right rail (bestsellers /
our stores / contact). All colors use the design tokens so light + dark
both work; the brand accent is the medical blue set in app.css. #}
<div class="grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_19rem]">
<div class="grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_19rem] lg:grid-rows-[auto_1fr] lg:items-start">
<!-- bestsellers (reuses the featured products). DOM-first so it stacks above
the product grid on mobile; placed in the right rail's top cell on lg. -->
{% if products | length > 0 %}
<section class="overflow-hidden rounded-radius border border-outline bg-surface-alt lg:col-start-2 lg:row-start-1 dark:border-outline-dark dark:bg-surface-dark-alt">
<h2 class="border-b border-outline px-4 py-3.5 text-xs font-bold uppercase tracking-wider text-on-surface-strong dark:border-outline-dark dark:text-on-surface-dark-strong">{{ t(key="home-bestsellers", lang=L) }}</h2>
<ol class="p-2">
{% for product in products | slice(end=5) %}
<li>
<a href="/shop/{{ product.slug }}" class="flex items-center gap-3 rounded-radius px-2 py-2 transition hover:bg-primary/5">
<span class="inline-flex size-6 shrink-0 items-center justify-center rounded-md bg-primary/10 text-xs font-extrabold text-primary dark:bg-primary-dark/15 dark:text-primary-dark">{{ loop.index }}</span>
<span class="flex size-11 shrink-0 items-center justify-center overflow-hidden rounded-md border border-outline bg-surface dark:border-outline-dark dark:bg-surface-dark">
{% if product.image %}
<img src="/images/{{ product.image }}" alt="{{ product.name }}" class="size-full object-cover">
{% else %}
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" class="text-on-surface/30 dark:text-on-surface-dark/30"><rect x="3" y="4" width="18" height="16" rx="2"></rect><circle cx="8.5" cy="9" r="1.6"></circle><path d="M21 16l-5-5L5 20"></path></svg>
{% endif %}
</span>
<span class="flex min-w-0 flex-col gap-0.5">
<span class="line-clamp-2 text-[13px] font-semibold leading-tight text-on-surface-strong dark:text-on-surface-dark-strong">{{ product.name }}</span>
<span class="text-sm font-extrabold text-primary dark:text-primary-dark">{% if product.has_options %}{{ t(key="from-price", price=product.price, lang=lang | default(value='sk')) }}{% else %}{{ product.price }}{% endif %} {{ currency_symbol }}</span>
</span>
</a>
</li>
{% endfor %}
</ol>
<a href="/shop" class="block border-t border-outline px-4 py-3 text-center text-[13px] font-semibold text-primary transition hover:bg-primary/5 dark:border-outline-dark dark:text-primary-dark">{{ t(key="home-bestsellers-all", lang=L) }}</a>
</section>
{% endif %}
<!-- center column -->
<div class="flex min-w-0 flex-col gap-6">
<div class="flex min-w-0 flex-col gap-6 lg:col-start-1 lg:row-span-2 lg:row-start-1">
<!-- hero / heading -->
<section>
<h1 class="text-3xl font-extrabold tracking-tight text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="shop-title", lang=lang | default(value='sk')) }}</h1>
@@ -34,7 +64,12 @@
<h2 class="text-xl font-bold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="nav-shop", lang=lang | default(value='sk')) }}</h2>
<a href="/shop" class="text-sm font-semibold text-primary dark:text-primary-dark">{{ t(key="cart-continue", lang=lang | default(value='sk')) }} →</a>
</div>
<div x-data="{ view: 'grid' }" class="grid grid-cols-2 gap-4 sm:grid-cols-3">
<div x-data="{ view: localStorage.getItem('shopView') === 'grid' ? 'grid' : 'list' }"
x-init="$watch('view', v => localStorage.setItem('shopView', v))"
{# Fixed-width cards (14rem), identical to the shop. Cards never stretch;
the column just fits as many as it can (home fewer, shop more), so a
card is the exact same width on both pages regardless of column count. #}
:class="view === 'list' ? 'flex flex-col gap-5' : 'grid grid-cols-2 gap-5 sm:grid-cols-[repeat(auto-fill,14rem)] sm:justify-center'">
{% for product in products %}
{% include "shop/_card.html" %}
{% endfor %}
@@ -49,55 +84,28 @@
</div>
<!-- right rail -->
<aside class="flex flex-col gap-5">
<!-- bestsellers (reuses the featured products) -->
{% if products | length > 0 %}
<section class="overflow-hidden rounded-radius border border-outline bg-surface-alt dark:border-outline-dark dark:bg-surface-dark-alt">
<h2 class="border-b border-outline px-4 py-3.5 text-xs font-bold uppercase tracking-wider text-on-surface-strong dark:border-outline-dark dark:text-on-surface-dark-strong">Najpredávanejšie</h2>
<ol class="p-2">
{% for product in products | slice(end=5) %}
<li>
<a href="/shop/{{ product.slug }}" class="flex items-center gap-3 rounded-radius px-2 py-2 transition hover:bg-primary/5">
<span class="inline-flex size-6 shrink-0 items-center justify-center rounded-md bg-primary/10 text-xs font-extrabold text-primary dark:bg-primary-dark/15 dark:text-primary-dark">{{ loop.index }}</span>
<span class="flex size-11 shrink-0 items-center justify-center overflow-hidden rounded-md border border-outline bg-surface dark:border-outline-dark dark:bg-surface-dark">
{% if product.image %}
<img src="/images/{{ product.image }}" alt="{{ product.name }}" class="size-full object-cover">
{% else %}
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" class="text-on-surface/30 dark:text-on-surface-dark/30"><rect x="3" y="4" width="18" height="16" rx="2"></rect><circle cx="8.5" cy="9" r="1.6"></circle><path d="M21 16l-5-5L5 20"></path></svg>
{% endif %}
</span>
<span class="flex min-w-0 flex-col gap-0.5">
<span class="line-clamp-2 text-[13px] font-semibold leading-tight text-on-surface-strong dark:text-on-surface-dark-strong">{{ product.name }}</span>
<span class="text-sm font-extrabold text-primary dark:text-primary-dark">{% if product.has_options %}{{ t(key="from-price", price=product.price, lang=lang | default(value='sk')) }}{% else %}{{ product.price }}{% endif %} {{ currency_symbol }}</span>
</span>
</a>
</li>
{% endfor %}
</ol>
<a href="/shop" class="block border-t border-outline px-4 py-3 text-center text-[13px] font-semibold text-primary transition hover:bg-primary/5 dark:border-outline-dark dark:text-primary-dark">Všetko najpredávanejšie </a>
</section>
{% endif %}
<aside class="flex flex-col gap-5 lg:col-start-2 lg:row-start-2">
<!-- our stores (static) -->
<section class="overflow-hidden rounded-radius border border-outline bg-surface-alt dark:border-outline-dark dark:bg-surface-dark-alt">
<h2 class="border-b border-outline px-4 py-3.5 text-xs font-bold uppercase tracking-wider text-on-surface-strong dark:border-outline-dark dark:text-on-surface-dark-strong">Naše obchody</h2>
<h2 class="border-b border-outline px-4 py-3.5 text-xs font-bold uppercase tracking-wider text-on-surface-strong dark:border-outline-dark dark:text-on-surface-dark-strong">{{ t(key="footer-stores", lang=L) }}</h2>
<div class="p-3.5">
<div class="flex h-28 items-center justify-center rounded-radius border border-outline bg-surface text-sm text-on-surface/50 dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark/40">Foto predajne</div>
<a href="/predajne" class="mt-3 inline-block text-sm font-bold text-primary transition hover:underline dark:text-primary-dark">Objaviť naše obchody </a>
<img src="/static/img/store.jpg" alt="{{ t(key='home-stores-photo', lang=L) }}" width="142" height="115" loading="lazy"
class="h-28 w-full rounded-radius border border-outline object-cover dark:border-outline-dark" />
<a href="/predajne" class="mt-3 inline-block text-sm font-bold text-primary transition hover:underline dark:text-primary-dark">{{ t(key="home-stores-discover", lang=L) }}</a>
</div>
</section>
<!-- contact CTA (static, brand blue) -->
<section class="overflow-hidden rounded-radius bg-primary text-on-primary dark:bg-primary-dark dark:text-on-primary-dark">
<section class="overflow-hidden rounded-radius bg-cta text-on-cta dark:bg-cta-dark dark:text-on-cta-dark">
<div class="p-5">
<div class="text-xs font-bold uppercase tracking-wider opacity-80">Kontaktujte nás</div>
<p class="mt-2.5 text-sm leading-relaxed opacity-90">Naša horúca linka je dostupná 24/7. Radi vám poradíme s výberom.</p>
<div class="text-xs font-bold uppercase tracking-wider opacity-80">{{ t(key="home-contact-title", lang=L) }}</div>
<p class="mt-2.5 text-sm leading-relaxed opacity-90">{{ t(key="home-contact-text", lang=L) }}</p>
<a href="tel:+421903410476" class="mt-3.5 flex items-center gap-2.5 text-xl font-extrabold tracking-tight">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M5 4h4l2 5-3 2a12 12 0 0 0 5 5l2-3 5 2v4a2 2 0 0 1-2 2A16 16 0 0 1 3 6a2 2 0 0 1 2-2Z"></path></svg>
+421 903 410 476
</a>
<a href="tel:+421903410476" class="mt-3.5 block w-full rounded-radius bg-surface px-4 py-3 text-center text-sm font-bold text-primary transition hover:opacity-90 dark:bg-surface-dark dark:text-primary-dark">Kontaktujte hotline</a>
<a href="tel:+421903410476" class="mt-3.5 block w-full rounded-radius bg-surface px-4 py-3 text-center text-sm font-bold text-cta transition hover:opacity-90 dark:bg-surface-dark dark:text-cta-dark">{{ t(key="home-contact-cta", lang=L) }}</a>
</div>
</section>

View File

@@ -36,7 +36,7 @@
<input type="hidden" name="_csrf" value="{{ csrf_token() }}">
{%- endmacro %}
{% macro button(label, variant="primary", type="button", href="", attrs="", extra="", icon="", size="px-4 py-2 text-sm") -%}
{% macro button(label, variant="primary", type="button", href="", attrs="", extra="", icon="", size="px-4 py-2 text-sm", nowrap=true) -%}
{%- if variant == "secondary" -%}{% set cls = "border border-secondary bg-secondary text-on-secondary focus-visible:outline-secondary dark:border-secondary-dark dark:bg-secondary-dark dark:text-on-secondary-dark dark:focus-visible:outline-secondary-dark" -%}
{%- elif variant == "danger" -%}{% set cls = "border border-danger bg-danger text-on-danger focus-visible:outline-danger dark:bg-danger dark:border-danger dark:text-on-danger dark:focus-visible:outline-danger" -%}
{%- elif variant == "success" -%}{% set cls = "border border-success bg-success text-on-success focus-visible:outline-success dark:bg-success dark:border-success dark:text-on-success dark:focus-visible:outline-success" -%}
@@ -49,9 +49,9 @@
{%- elif variant == "ghost-primary" -%}{% set cls = "bg-transparent text-primary focus-visible:outline-primary dark:text-primary-dark dark:focus-visible:outline-primary-dark" -%}
{%- elif variant == "ghost-secondary" -%}{% set cls = "bg-transparent text-secondary focus-visible:outline-secondary dark:text-secondary-dark dark:focus-visible:outline-secondary-dark" -%}
{%- elif variant == "ghost-danger" -%}{% set cls = "bg-transparent text-danger focus-visible:outline-danger dark:text-danger dark:focus-visible:outline-danger" -%}
{%- else -%}{% set cls = "border border-primary bg-primary text-on-primary focus-visible:outline-primary dark:border-primary-dark dark:bg-primary-dark dark:text-on-primary-dark dark:focus-visible:outline-primary-dark" -%}
{%- else -%}{% set cls = "border border-cta bg-cta text-on-cta focus-visible:outline-cta dark:border-cta-dark dark:bg-cta-dark dark:text-on-cta-dark dark:focus-visible:outline-cta-dark" -%}
{%- endif -%}
{% if href %}<a href="{{ href }}"{% else %}<button type="{{ type }}"{% endif %} class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-radius {{ size }} text-center font-medium tracking-wide transition hover:opacity-75 focus-visible:outline-2 focus-visible:outline-offset-2 active:opacity-100 active:outline-offset-0 disabled:cursor-not-allowed disabled:opacity-75 {{ cls }} {{ extra }}" {{ attrs | safe }}>{{ icon | safe }}{{ label }}</{% if href %}a{% else %}button{% endif %}>
{% if nowrap %}{% set wrap = "whitespace-nowrap" %}{% else %}{% set wrap = "text-balance" %}{% endif %}{% if href %}<a href="{{ href }}"{% else %}<button type="{{ type }}"{% endif %} class="inline-flex items-center justify-center gap-2 {{ wrap }} rounded-radius {{ size }} text-center font-medium tracking-wide transition hover:opacity-75 focus-visible:outline-2 focus-visible:outline-offset-2 active:opacity-100 active:outline-offset-0 disabled:cursor-not-allowed disabled:opacity-75 {{ cls }} {{ extra }}" {{ attrs | safe }}>{{ icon | safe }}{{ label }}</{% if href %}a{% else %}button{% endif %}>
{%- endmacro button %}
{# Icon-only button (square). Penguin ghost treatment (bg-transparent,

View File

@@ -57,6 +57,38 @@
{% endif %}
</ul>
{% elif page == "stores" %}
{# Production facility (not a retail store): intro, Google map on top, then a
small facility photo next to the address card. #}
<p class="text-lg text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="page-stores-intro", lang=L) }}</p>
{# Google Map of the production facility. Embedded via the keyless Maps embed
(centered on the geocoded coords); a static PNG would need a Maps Static
API key. The header links out to the full map. #}
<section class="overflow-hidden rounded-radius border border-outline bg-surface-alt dark:border-outline-dark dark:bg-surface-dark-alt">
<div class="flex items-center justify-between gap-3 border-b border-outline px-4 py-3 dark:border-outline-dark">
<h2 class="text-sm font-bold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="page-stores-map", lang=L) }}</h2>
<a href="https://www.google.com/maps/search/?api=1&query=49.092412,18.643697" target="_blank" rel="noopener"
class="text-sm font-semibold text-primary transition hover:underline dark:text-primary-dark">{{ t(key="page-stores-map-open", lang=L) }}</a>
</div>
<iframe title="{{ t(key='page-stores-map', lang=L) }}" loading="lazy" class="block h-72 w-full border-0 sm:h-96"
referrerpolicy="no-referrer-when-downgrade"
src="https://maps.google.com/maps?q=49.092412,18.643697&z=15&hl={{ L }}&output=embed"></iframe>
</section>
{# Small facility photo next to the address. #}
<div class="flex flex-col gap-4 sm:flex-row sm:items-stretch">
<figure class="shrink-0 overflow-hidden rounded-radius border border-outline bg-surface-alt dark:border-outline-dark dark:bg-surface-dark-alt sm:w-48">
<img src="/static/img/store.jpg" alt="{{ t(key='home-stores-photo', lang=L) }}" width="142" height="115" loading="lazy"
class="h-32 w-full object-cover sm:h-full" />
</figure>
<div class="flex-1 rounded-radius border border-outline bg-surface-alt p-4 dark:border-outline-dark dark:bg-surface-dark-alt">
<div class="text-xs font-semibold uppercase tracking-wide text-on-surface/50 dark:text-on-surface-dark/50">{{ t(key="page-stores-address-label", lang=L) }}</div>
<div class="mt-1 font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="page-stores-facility", lang=L) }}</div>
<address class="mt-1 not-italic text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="page-stores-address", lang=L) }}</address>
</div>
</div>
{% else %}
<div class="rounded-radius border border-outline bg-surface-alt p-6 text-on-surface/70 dark:border-outline-dark dark:bg-surface-dark-alt dark:text-on-surface-dark/70">
<p>{{ t(key="page-coming-soon", lang=L) }}</p>

View File

@@ -12,8 +12,8 @@
for why — htmx hx-boost settles by id). #}
<div x-data="{ isOpen: false, openedWithKeyboard: false }"
x-on:keydown.esc.window="isOpen = false, openedWithKeyboard = false"
class="relative">
{{ ui::icon_button(aria_label=t(key='settings', lang=lang | default(value='sk')), attrs='x-on:click="isOpen = ! isOpen" x-on:keydown.space.prevent="openedWithKeyboard = true" x-on:keydown.enter.prevent="openedWithKeyboard = true" x-on:keydown.down.prevent="openedWithKeyboard = true" x-bind:aria-expanded="isOpen || openedWithKeyboard" aria-haspopup="true"', icon='<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5"><path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" /><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" /></svg>') }}
class="relative self-stretch">
{{ ui::icon_button(size="h-full w-9", aria_label=t(key='settings', lang=lang | default(value='sk')), attrs='x-on:click="isOpen = ! isOpen" x-on:keydown.space.prevent="openedWithKeyboard = true" x-on:keydown.enter.prevent="openedWithKeyboard = true" x-on:keydown.down.prevent="openedWithKeyboard = true" x-bind:aria-expanded="isOpen || openedWithKeyboard" aria-haspopup="true"', icon='<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5"><path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" /><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" /></svg>') }}
<div x-cloak x-show="isOpen || openedWithKeyboard" x-transition x-trap="openedWithKeyboard"
x-on:click.outside="isOpen = false, openedWithKeyboard = false"
x-on:keydown.down.prevent="$focus.wrap().next()" x-on:keydown.up.prevent="$focus.wrap().previous()"

View File

@@ -12,12 +12,12 @@
that state (e.g. home) `view` is undefined, so the grid layout applies. #}
<article
class="group flex overflow-hidden rounded-radius border border-outline bg-surface-alt text-on-surface transition hover:border-primary dark:border-outline-dark dark:bg-surface-dark-alt dark:text-on-surface-dark dark:hover:border-primary-dark"
:class="view === 'list' ? 'flex-row flex-wrap' : 'flex-col'">
:class="view === 'list' ? 'flex-col sm:flex-row' : 'flex-col'">
<a href="/shop/{{ product.slug }}" class="flex min-w-0 flex-1"
:class="view === 'list' ? 'flex-row' : 'flex-col'">
<!-- Image -->
<div class="relative overflow-hidden bg-surface-alt dark:bg-surface-dark"
:class="view === 'list' ? 'size-28 shrink-0 sm:size-40' : 'h-44 md:h-64'">
:class="view === 'list' ? 'w-28 shrink-0 self-stretch min-h-36 sm:w-48' : 'aspect-[5/4]'">
{% if product.on_sale and product.percent_off > 0 %}
<span class="absolute left-2 top-2 z-10 rounded-full bg-danger px-2 py-0.5 text-[11px] font-bold text-on-danger shadow-sm">{{ product.percent_off }} %</span>
{% endif %}
@@ -27,7 +27,7 @@
</div>
<!-- Content -->
<div class="flex min-w-0 flex-1 flex-col gap-1"
:class="view === 'list' ? 'p-4 sm:p-5' : 'p-6 pb-2'">
:class="view === 'list' ? 'justify-center p-4 sm:p-5' : 'px-4 pt-3 pb-1'">
<!-- Header: Title & Price (stacked so neither overflows the narrow card) -->
<h3 class="break-words text-lg font-bold text-on-surface-strong dark:text-on-surface-dark-strong">{{ product.name }}</h3>
{# Short blurb for the card; falls back to the full description (clamped)
@@ -62,10 +62,10 @@
</div>
</a>
<div class="flex flex-col gap-2"
:class="view === 'list' ? 'w-full justify-center p-4 sm:w-56 sm:p-5' : 'p-6 pt-0'">
:class="view === 'list' ? 'w-full justify-center border-t border-outline p-4 sm:w-48 sm:self-stretch sm:border-l sm:border-t-0 sm:p-5 dark:border-outline-dark' : 'px-4 pb-4 pt-0'">
{% if product.has_options %}
{# Multiple variants: customer must pick on the product page. #}
{{ ui::button(label=t(key="choose-option", lang=lang | default(value='sk')), href="/shop/" ~ product.slug, extra="w-full") }}
{{ ui::button(label=t(key="choose-option", lang=lang | default(value='sk')), href="/shop/" ~ product.slug, extra="w-full", nowrap=false) }}
{% elif product.in_stock %}
<p class="text-xs text-on-surface/60 dark:text-on-surface-dark/60">{% if product.tracked %}{{ t(key="in-stock", lang=lang | default(value='sk')) }}: {{ product.stock }}{% else %}{{ t(key="available", lang=lang | default(value='sk')) }}{% endif %}</p>
<form method="post" action="/cart/add" hx-post="/cart/add" hx-swap="none"
@@ -73,7 +73,7 @@
<input type="hidden" name="_csrf" value="{{ csrf_token() }}">
<input type="hidden" name="variant_id" value="{{ product.variant_id }}">
<input type="hidden" name="quantity" value="1">
{{ ui::button(label=t(key="add-to-cart", lang=lang | default(value='sk')), type="submit", extra="w-full", icon='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" class="size-3.5"><path fill-rule="evenodd" d="M5 4a3 3 0 0 1 6 0v1h.643a1.5 1.5 0 0 1 1.492 1.35l.7 7A1.5 1.5 0 0 1 12.342 15H3.657a1.5 1.5 0 0 1-1.492-1.65l.7-7A1.5 1.5 0 0 1 4.357 5H5V4Zm4.5 0v1h-3V4a1.5 1.5 0 0 1 3 0Zm-3 3.75a.75.75 0 0 0-1.5 0v1a3 3 0 1 0 6 0v-1a.75.75 0 0 0-1.5 0v1a1.5 1.5 0 1 1-3 0v-1Z" clip-rule="evenodd" /></svg>') }}
{{ ui::button(label=t(key="add-to-cart", lang=lang | default(value='sk')), type="submit", extra="w-full", nowrap=false, icon='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" class="size-3.5 shrink-0"><path fill-rule="evenodd" d="M5 4a3 3 0 0 1 6 0v1h.643a1.5 1.5 0 0 1 1.492 1.35l.7 7A1.5 1.5 0 0 1 12.342 15H3.657a1.5 1.5 0 0 1-1.492-1.65l.7-7A1.5 1.5 0 0 1 4.357 5H5V4Zm4.5 0v1h-3V4a1.5 1.5 0 0 1 3 0Zm-3 3.75a.75.75 0 0 0-1.5 0v1a3 3 0 1 0 6 0v-1a.75.75 0 0 0-1.5 0v1a1.5 1.5 0 1 1-3 0v-1Z" clip-rule="evenodd" /></svg>') }}
</form>
{% else %}
<p class="inline-flex justify-center rounded-radius bg-danger/10 px-3 py-2 text-xs font-medium text-danger">{{ t(key="out-of-stock", lang=lang | default(value='sk')) }}</p>

View File

@@ -2,7 +2,10 @@
wrapper in _search.html (it persists across htmx swaps and is shared with the
sort + view-toggle row); `_card.html` reads the same `view` to switch its own
layout between a vertical card and a horizontal row. #}
<div :class="view === 'list' ? 'flex flex-col gap-5' : 'grid grid-cols-2 gap-5 sm:grid-cols-3 xl:grid-cols-4'">
{# Fixed-width cards (14rem) — same as the home page. Cards never stretch; the row
just fits as many as the width allows. This keeps a card the exact same width on
the shop and the home page regardless of how many columns fit. #}
<div :class="view === 'list' ? 'flex flex-col gap-5' : 'grid grid-cols-2 gap-5 sm:grid-cols-[repeat(auto-fill,14rem)] sm:justify-center'">
{% for product in products %}
{% include "shop/_card.html" %}
{% endfor %}

View File

@@ -10,7 +10,7 @@
Expects: query, selected_category, sort, plus the result vars consumed by
_results.html. #}
{% set L = lang | default(value='sk') %}
<div x-data="{ view: localStorage.getItem('shopView') === 'list' ? 'list' : 'grid' }"
<div x-data="{ view: localStorage.getItem('shopView') === 'grid' ? 'grid' : 'list' }"
x-init="$watch('view', v => localStorage.setItem('shopView', v))"
class="space-y-6">
<form action="/search" method="get" role="search"
@@ -42,7 +42,7 @@
</svg>
</span>
</div>
<button type="submit" class="shrink-0 rounded-radius bg-primary px-5 text-sm font-bold text-on-primary transition hover:opacity-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary dark:bg-primary-dark dark:text-on-primary-dark dark:focus-visible:outline-primary-dark">
<button type="submit" class="shrink-0 rounded-radius bg-cta px-5 text-sm font-bold text-on-cta transition hover:opacity-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-cta dark:bg-cta-dark dark:text-on-cta-dark dark:focus-visible:outline-cta-dark">
{{ t(key="search-button", lang=L) }}
</button>
</div>
@@ -62,7 +62,13 @@
{{ ui::icon(name="search", size="size-3.5", extra="shrink-0") }}
{{ t(key="search-scope-in", lang=L) }} <span class="font-semibold">{{ _scope }}</span>
</span>
{# This link descends from the search form, so it inherits its
hx-target="#shop-results" / hx-swap="innerHTML". Switching scope is a
real navigation (new breadcrumb, sidebar state, full-page response),
so override the inherited target back to the body — otherwise the
boosted full page gets nested inside the results region. #}
<a href="/search{% if query %}?q={{ query | urlencode }}{% endif %}"
hx-target="body" hx-swap="innerHTML"
class="font-medium text-on-surface/60 underline-offset-2 hover:text-primary hover:underline dark:text-on-surface-dark/60 dark:hover:text-primary-dark">
{{ t(key="search-scope-all", lang=L) }}
</a>

View File

@@ -16,6 +16,11 @@
{{ t(key="categories", lang=lang | default(value='sk')) }}
</p>
<div class="flex flex-col gap-1">
{# mobile-only Home link: the navbar logo (the Home affordance) is hidden on
small screens, so navigation home lives here in the drawer instead. #}
<a href="/" data-nav="/" class="flex items-center gap-2 rounded-radius px-2 py-1.5 text-sm font-medium text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-on-surface-strong focus:outline-hidden focus-visible:underline aria-[current=page]:bg-primary/10 aria-[current=page]:text-on-surface-strong lg:hidden dark:text-on-surface-dark dark:hover:bg-primary-dark/5 dark:hover:text-on-surface-dark-strong dark:aria-[current=page]:bg-primary-dark/10 dark:aria-[current=page]:text-on-surface-dark-strong">
{{ t(key="nav-home", lang=lang | default(value='sk')) }}
</a>
<a href="/shop" data-nav="/shop"
class="flex items-center gap-2 rounded-radius px-2 py-1.5 text-sm font-medium text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-on-surface-strong focus:outline-hidden focus-visible:underline aria-[current=page]:bg-primary/10 aria-[current=page]:text-on-surface-strong dark:text-on-surface-dark dark:hover:bg-primary-dark/5 dark:hover:text-on-surface-dark-strong dark:aria-[current=page]:bg-primary-dark/10 dark:aria-[current=page]:text-on-surface-dark-strong">
{{ t(key="all-products", lang=lang | default(value='sk')) }}
@@ -71,7 +76,6 @@
{% set L = lang | default(value='sk') %}
<div class="flex flex-col gap-0.5">
<a href="/obchodne-podmienky" class="flex items-center gap-2 truncate rounded-radius px-2 py-1.5 text-sm text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-on-surface-strong focus:outline-hidden focus-visible:underline dark:text-on-surface-dark dark:hover:bg-primary-dark/5 dark:hover:text-on-surface-dark-strong">{{ t(key="footer-terms", lang=L) }}</a>
<a href="/o-nas" class="flex items-center gap-2 truncate rounded-radius px-2 py-1.5 text-sm text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-on-surface-strong focus:outline-hidden focus-visible:underline dark:text-on-surface-dark dark:hover:bg-primary-dark/5 dark:hover:text-on-surface-dark-strong">{{ t(key="footer-about", lang=L) }}</a>
<a href="/predajne" class="flex items-center gap-2 truncate rounded-radius px-2 py-1.5 text-sm text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-on-surface-strong focus:outline-hidden focus-visible:underline dark:text-on-surface-dark dark:hover:bg-primary-dark/5 dark:hover:text-on-surface-dark-strong">{{ t(key="footer-stores", lang=L) }}</a>
<a href="/doprava-a-platba" class="flex items-center gap-2 truncate rounded-radius px-2 py-1.5 text-sm text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-on-surface-strong focus:outline-hidden focus-visible:underline dark:text-on-surface-dark dark:hover:bg-primary-dark/5 dark:hover:text-on-surface-dark-strong">{{ t(key="footer-shipping", lang=L) }}</a>
</div>

View File

@@ -50,7 +50,7 @@
<!-- details -->
{% set fld = "w-full rounded-radius border border-outline bg-surface-alt px-3 py-2 text-sm text-on-surface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary dark:border-outline-dark dark:bg-surface-dark-alt/50 dark:text-on-surface-dark dark:focus-visible:outline-primary-dark" %}
{% set btn = "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-radius px-5 py-2 text-sm text-center font-medium tracking-wide transition hover:opacity-75 focus-visible:outline-2 focus-visible:outline-offset-2 active:opacity-100 active:outline-offset-0 disabled:cursor-not-allowed disabled:opacity-75 border border-primary bg-primary text-on-primary focus-visible:outline-primary dark:border-primary-dark dark:bg-primary-dark dark:text-on-primary-dark dark:focus-visible:outline-primary-dark" %}
{% set btn = "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-radius px-5 py-2 text-sm text-center font-medium tracking-wide transition hover:opacity-75 focus-visible:outline-2 focus-visible:outline-offset-2 active:opacity-100 active:outline-offset-0 disabled:cursor-not-allowed disabled:opacity-75 border border-cta bg-cta text-on-cta focus-visible:outline-cta dark:border-cta-dark dark:bg-cta-dark dark:text-on-cta-dark dark:focus-visible:outline-cta-dark" %}
<script id="variant-data" type="application/json">{{ variants | json_encode() | safe }}</script>
<div class="space-y-6" x-data="productBuy(JSON.parse(document.getElementById('variant-data').textContent))">
{% if category %}

View File

@@ -1,184 +0,0 @@
# Design parity — Kompress mockup vs current storefront
Tracks the gap between the imported Claude Design mockup (`Kompress-eshop.html`,
project `eshop` on claude.ai/design) and the live storefront. The home page body
(featured grid + right rail) and the medicalblue accent are **done**; this file
lists everything else from the mockup that is still missing or only halfwired, so
it can be picked up incrementally.
Constraints for all items: **htmx + Alpine + PenguinUI + Tailwind tokens only.**
Express colors with the existing design tokens (`bg-primary`, `bg-surface-alt`,
`border-outline`, `dark:*` …) — never inline hex — so light **and** dark both work.
PenguinUI is copypaste: lift markup out of `penguinui-components/<name>/…`
(readonly, never `{% include %}`'d) and adapt at the usesite.
Legend: 🟢 backend already exists, only UI missing · 🟡 partial · 🔴 netnew.
---
## A. Global chrome (`assets/views/base.html`)
These affect every page, so they live in `base.html`, not the home template.
> **✅ Implemented (pass 2)** — A1A6 are now built in `base.html`, with i18n keys
> added to `assets/i18n/{sk,en}/main.ftl` (`top-contact`, `search-button`,
> `welcome`, `brand-subtitle`, `footer-*`, `hotline`, …). Verified: `/`, `/shop`,
> `/login`, `/register`, `/cart`, `/search?q=` all 200, no render errors.
> The `Kontakt` / `Mapa stránky` top-bar links and the footer info links point to
> `#` until real pages exist.
### A1. Top utility bar — ✅ done
Mockup: thin white bar above the header, rightaligned links *Kontakt* · *Mapa
stránky*.
- New `<div>` strip at the very top of `base.html`, `border-b border-outline
bg-surface text-xs text-on-surface/70`.
- Links to a future `/kontakt` and `/sitemap` (or existing pages).
- Low effort, no backend.
### A2. Inheader search bar — ✅ done
Mockup: fullwidth search input with a magnifier icon + blue **Hľadať** submit
button, centered in the header.
- **Backend already exists**: `GET /search` (`src/controllers/shop.rs:353`,
`run_search` at `:90`, fragment `assets/views/shop/_search.html`). Just needs a
header form: `<form action="/search" method="get">` with `name="q"`, optionally
`hx-get="/search" hx-trigger="keyup changed delay:300ms"` for live results.
- Copy markup from `penguinui-components/navbar/with-search.html` and/or
`penguinui-components/text-input/search-input.html`.
- Medium effort — mostly wiring the existing endpoint into the header layout.
### A3. Logo lockup with subtitle — ✅ done
Mockup: rounded blue tile with a "+" cross glyph + **KOMPRESS** + uppercase
subtitle *zdravotnícke potreby*.
- Current logo is a plain text wordmark in `base.html`. Add the icon tile
(`bg-primary text-on-primary`, `rounded-radius`) and the muted subtitle line.
- Cross SVG is in the mockup (two rounded `<rect>`s). Low effort.
### A4. Account button (twoline) — ✅ done
Mockup: person icon + stacked *Vitajte* / *Prihláste sa* (or the customer name when
logged in).
- Loggedin/out state already available via `logged_in_customer` /
`customer_name` and the existing `partials/profile_menu.html`.
- Restyle the login link / profile menu trigger into the twoline button. Low effort.
### A5. Cart button with text label — ✅ done
Mockup: cart icon + red count badge + stacked *Košík* / *N produktov* label.
- The icon + cookiedriven badge already exist in `base.html` (`cartCount()` JS).
Add the text label beside it; pluralize "produkt/produkty/produktov" (mirror the
mockup's `plural()` helper, or add an i18n key).
- Low effort.
### A6. Footer — ✅ done
Mockup: 4column footer (brand blurb · Informácie · Účet · Kontakt) + copyright bar.
- **No footer exists anywhere** (`grep "<footer"` → none). Add once to `base.html`
after `<main>`.
- Static links; reuse the same i18n keys as the nav. Low effort, high visual payoff.
---
## B. Listing layout (`shop/category.html`, `shop/_product_grid.html`)
The mockup's body is really a **category/listing** layout. The home page borrows its
look; the real listing pages should adopt the rest.
> **✅ Implemented (pass 3)** — reusable `ui::crumb` / `ui::crumb_current` chevron
> breadcrumb macros (`assets/views/macros/ui.html`), wired into `home/index.html`
> (Domov), `shop/index.html` (Domov Obchod) and restyled on `shop/category.html`
> (Domov Obchod category). Added the sidebar **Informácie** card to
> `shop/_sidebar.html`. While verifying, confirmed **B3 sort**, **B4 view toggle**
> and **C1 collapsible tree** were already built — only styling polish remains.
> Verified: `/`, `/shop`, `/partials/categories`, `/search?q=` all 200, no errors.
### B1. Breadcrumbs — ✅ done
Mockup: *Domov Zdravotnícke {kategória}* with chevron separators.
- **Already implemented** on category pages: `breadcrumbs` loop in
`assets/views/shop/category.html:12`. Just needs the mockup's chevron styling and
to be surfaced on more pages (e.g. product detail, optionally a "Domov" crumb on
home).
- Copy from `penguinui-components/breadcrumbs/breadcrumb-with-chevron.html`.
- Low effort (restyle existing).
### B2. Category banner card — 🔴
Mockup: white card with a 148px image placeholder + category description paragraph +
**Viac ** link, shown above the product list.
- Percategory copy/image. Needs a `description` (and optional banner image) field on
the category model, or static copy keyed by slug for now.
- Render as the app card idiom (`border border-outline bg-surface-alt dark:…`).
- Medium effort (model field) or low (static placeholder first).
### B3. Sort control ("Zoradiť podľa") — ✅ already in toolbar
Mockup: styled `<select>` — Predvolené / Cena ↑ / Cena ↓ / Názov AZ /
Najpredávanejšie.
- **Backend already exists**: `sort` query param (`src/controllers/shop.rs:41,81,
179181`). Needs the styled select wired to it (`hx-get` on `change`, preserving
the query + filters via the existing `build_query` helper at `shop.rs:63`).
- Copy from `penguinui-components/select/default-select.html`.
- Low effort.
### B4. View grid/list toggle — 🟢 (done elsewhere)
- **Already implemented** in `assets/views/shop/_product_grid.html` (Alpine `view`
state, persisted to `localStorage`). The mockup's toggle is the same idea; just
align the icon/segmentedbutton styling with the mockup. Cosmetic only.
### B5. "Porovnať" (compare) — 🔴
Mockup: a *Porovnať* button in the listing toolbar.
- **No compare feature exists.** Netnew: selecttocompare state (Alpine + a cookie
or server list) and a compare view. Largest item here — defer unless wanted.
### B6. Listview product card — ✅ done
Mockup list rows are richer (left thumbnail, description, stock pill, price block,
add/options/outofstock button on the right).
- `shop/_card.html` already has a `view === 'list'` horizontal layout. Close already;
optionally add the stock pill (*Skladom* / *Vypredané*) and discount badge to match.
- Low effort.
---
## C. Left sidebar (`assets/views/partials/categories.html` via `/partials/categories`)
Mockup left column = two white cards: a collapsible **Kategórie** tree and an
**Informácie** links card. base.html already renders `#category-sidebar` from
`/partials/categories`, so this is a restyle of that partial.
### C1. Collapsible category tree — ✅ already implemented
Mockup: toplevel groups with +/ chevrons; expanded group shows leaf links, active
leaf highlighted (`bg-primary/10 text-primary font-bold`).
- Style the existing categories partial as a white card with Alpinetoggled
sublists (`x-data="{ open: … }"`). Active state via `aria-current` + the existing
`markActiveNav()` pattern.
- Medium effort.
### C2. "Informácie" links card — ✅ done
Mockup: second sidebar card with Obchodné podmienky / O našej spoločnosti / Naše
obchody / Doprava a platba.
- Static links card; same markup as the contact/stores cards already added to the
home right rail. Low effort.
---
## D. Right rail — mostly done
Implemented on the home page: **Najpredávanejšie** (from featured products), **Naše
obchody** placeholder, **Kontaktujte nás** blue card.
Remaining 🟡:
- **D1. Bestsellers from real sales data** — currently reuses the featured query;
wire to actual order/sales counts when available.
- **D2. "Naše obchody" real photo + page** — replace the placeholder block and link
to a real stores page.
- **D3. Contact hotline** — phone number is hardcoded; move to config/i18n.
---
## Suggested order (quick wins first)
1. Footer (A6) + top utility bar (A1) — pure markup, big visual lift.
2. Header search (A2) + cart label (A5) + logo lockup (A3) — wire existing data.
3. Sort select (B3) + breadcrumb restyle (B1) — backends already exist.
4. Sidebar restyle (C1, C2).
5. Category banner (B2), richer list card (B6).
6. Compare (B5) — only if the feature is actually wanted.
## Reference
- Mockup source extracted to (scratch, regenerate via DesignSync if needed):
`DesignSync get_file` on project `015b8bf5-fd27-4a7e-82d9-d1864a59578c`,
file `Kompress-eshop.html` → `__bundler/template` script holds the real markup.
- PenguinUI workflow: see `penguinui-workflow` memory + `hardcoded-inventory.md`.

View File

@@ -0,0 +1,123 @@
# Real data to port from www.e-shop.kompress.sk
Source: <http://e-shop.kompress.sk/> (live PrestaShop site). This file lists the
real business content that exists on the production site but is **not** yet in
this app, so it can be ported into our catalog / CMS / config.
> Status note: the header branding has already been switched from the
> placeholder "Kompress eshop" to the real logo (`assets/static/img/logo.jpg`,
> the blue **KOMPRESS** wordmark pulled from `/img/logo.jpg` on the live site)
> and the `brand` / `meta-description` i18n keys now carry the real company
> name and tagline.
---
## 1. Company / branding
| Field | Real value |
|---|---|
| Legal name | **WWW.KOMPRESS.SK, s.r.o.** |
| Display name | www.e-shop.kompress.sk |
| Tagline (meta) | *Výrobca a distribútor zdravotníckych pomôcok a potrieb* (Manufacturer and distributor of medical aids and supplies) |
| Logo | blue **KOMPRESS** wordmark — `/img/logo.jpg` (260×52), saved to `assets/static/img/logo.jpg` |
| Keywords | obchod, výroba, distribúcia, striekačky, ihly, krytie, sušenie, jednorázový, materiál, štvorce |
## 2. Contact / legal (for footer + invoicing config)
- **Sídlo (registered seat):** Gunduličova 4, 811 05 Bratislava
*(the homepage footer block also shows "Moyzesova 3, 811 05 Bratislava" — confirm which is current)*
- **Prevádzka (operations / warehouse):** Nádražná 328/62, 015 01 Rajec nad Rajčankou
- **Registrácia:** Obchodný register Okresného súdu v Bratislave, Odd. Sro, Vložka číslo: 102522/B
- **Telefón / hotline:** +421 903 410476
- **E-mail:** kompress@kompress.sk
## 3. "O našej spoločnosti" (About) — CMS page
> WWW.KOMPRESS.SK, s.r.o., Sídlo: Gunduličova 4, 811 05 Bratislava, Prevádzka:
> Nádražná 328/62, 015 01 Rajec nad Rajčankou, Registrácia: Obchodný register
> Okresného súdu v Bratislave Odd. Sro, Vložka číslo: 102522/B. Spoločnosť
> dodáva zdravotnícke potreby a svojich zákazníkov tradične oslovuje vysokou
> kvalitou a nízkou cenou. Samozrejmosťou je doprava tovaru až k rukám
> odberateľa a kvalitný zákaznícky servis.
Other CMS pages present on the live site:
- `O našej spoločnosti``/content/4-kompress`
- `Podmienky používania obchodu` (terms) — `/content/3-podmienky-pouzivania-obchodu`
## 4. Product categories (PrestaShop IDs → name / description)
These are the real catalog categories. Top-level is **Zdravotnícke pomôcky**
(id 7); the rest are its subtree. The text below is the real category
description copy to port.
| ID | Category / description copy |
|---|---|
| 7 | **Zdravotnícke pomôcky** (root) |
| 8 | **Gáza, role, štvorce, prírezy** — Gáza a výrobky z gázy, prírezy, role, zložky, kompresy resp. štvorce, pásy resp. longety, sterilné či nesterilné. Čistá biela bavlna. |
| 9 | **Vata** (buničitá, obväzová, stomatologická) — bielená, v rezoch, v návine, delená na tampóny, skladaná ako harmonika, savá a mäkká. |
| 10 | **Netkané textílie** — Pervin, SMS obaly na sterilizáciu, návleky na obuv. |
| 11 | **Textil, Sanavel** |
| 12 | **Plasty, injekčná technika** — vrecká, tŕne, hadičky, ihly, striekačky, infúzne súpravy, urínové vrecká, odberové vaky, cievky, katétre Nelaton, odsávačky. |
| 13 | **Papier na lôžko** — krepovaný biely/farebný, tissue embosovaný, laminovaný tissue s plastovou spodnou vrstvou. |
| 14 | **Somatické / psycho-somatické prístroje** — pomôcky na elimináciu geopatogénnych zón, elektrosmogu. |
| 15 | **Somavedic** (rad prístrojov) — eliminácia vplyvu geopatogénnych zón, elektrosmogu; dosah 30 m. |
| 16 | **Úprava vody** — voda ako nosič energie. |
| 17 | **Gely a lubrikanty** — gély na sonografiu/ECG, lubrikanty na sondy; balenia 260/500/1000 ml, kanister 5 l. |
| 18 | **Somavedic AURUM** — najúčinnejší z kolekcie. |
| 19 | **Tecasorb** — moderné absorpčné krytie (vlhké aj suché). |
| 20 | (Somavedic — biofyzikálne sledovanie IEGF) |
| 21 | **Obväzy** — fixačné, elastické ovínadlá, sádrové obväzy, gumové škrtidlá MARTIN, ESMARCH. |
| 22 | **Nástroje** — čepelky, peany, nožničky, pinzety. |
| 23 | **Pre invalidov** — vozíky, postele, matrace, návleky, poťahy, stoličky, nádstavce na WC, antidekubitné matrace. |
| 24 | **Proti preležaninám** — antidekubitné matrace, poťahy, podložky, sedáky, motorové pumpy. |
| 25 | **Barle, palice, chodúliky** — chodítka, statické/pohyblivé chodúliky s kolieskami. |
| 26 | **Vozíky pre invalidov - mechanické** — bez pohonu, polohovateľné, skladacie. |
| 27 | **Držadlá, nástavce na WC, operadlá do kúpeľne** — namontovateľné na stenu/umývadlo/WC. |
| 28 | **Ochrana matracov** — umývateľné poťahy, návleky, obliečky. |
| 29 | **Ortopedické pomôcky** — vložky do topánok, silikónové, medziprstové, gelové. |
| 30 | **Polohovacie pomôcky** — podložky s výrezom, sedáky, krúžky, valce. |
| 31 | **Do kúpeľne** — stolička/sedačka/opierka/držadlo do vane a sprchy, protišmykové, nastaviteľné. |
| 32 | **Relaxácia a rehabilitácia** — lopty, balóny, gumové pásy, pedále, masážne loptičky. |
| 33 | **Sebaobsluha a obsluha pacienta** — obúvanie, poháre, pásy na dvíhanie, sklápacie stolíky. |
| 34 | **Toaletné kreslá a toaletné vozíky** — kreslá/vozíky s otvorom v sedadle, kreslo do sprchy. |
| 35 | **Somavedic MEDIC** — certifikát IGEF na rušenie elektrosmogu. |
| 36 | **Rukavice** — vyšetrovacie, chirurgické bezpúdrové/púdrované, latexové, nitrilové, vinylové, neoprénové. |
## 5. Featured products (homepage) with prices
Prices are EUR (display on site uses `X,XX €`). Image paths are on the live
site under `/<id>-home_default/<slug>.jpg`.
| Product | Price (€) | Notes |
|---|---|---|
| Somavedic URAN | 600,00 | id 581 |
| Somavedic MEDIC (elektrosmog eliminátor) | 360,00 | id 213 |
| Somavedic ATLANTIK | 300,00 | id 572 |
| Sedačka do sprchy nastaviteľná, s opierkou chrbta a výrezom | 65,00 | id 497 |
| Opierka pod chrbát polohovateľná | 37,20 | id 378 |
| Krepovaný papier na lôžko (papier na operačné stoly) | 4,80 | id 248 |
| GAMMEX chirurgické rukavice pár | 1,23 | id 582 |
| Gáza v páse s buničitou vatou (Mullro) | 1,14 / 1,23 | id 567 |
| Návleky na topánky NT | 0,12 | id 224 |
| Návlek na obuv (plastový s gumičkou) | 0,04 | id 279 |
| Gázové kompresy, štvorce nesterilné | — | id 100 |
Other Somavedic models referenced on site: **Somavedic AURUM** (id 18 cat).
## 6. Storefront blocks present on live site (UX reference)
- Home slider (homeslider) — "pomôcky pre pacientov", "prístroj Somavedic aurum".
- "Naše obchody" (Our stores / blockstore) block.
- Reinsurance block (5 trust badges) — `blockreinsurance`.
- Price-comparison badges: Pricemania, Heureka.sk, Tovar.sk, NajNakup.sk.
## 7. Suggested port order
1. **Config/contact** — drop real legal name, seat, ops address, IČO/registration,
phone, email into footer + invoicing config (see `account-type-rules` memory).
2. **CMS pages** — seed `O našej spoločnosti` and `Obchodné podmienky` content.
3. **Categories** — seed categories 736 (names + descriptions above) under root
"Zdravotnícke pomôcky"; map to our category model.
4. **Products** — import the featured products with prices/variants; pull images
from `/<id>-home_default/` on the live site.
5. **Currency** — site prices are EUR (matches our EUR base; CZK display optional).