new ui2
This commit is contained in:
@@ -500,3 +500,24 @@ order-manual-fulfillment = Manual fulfilment — no carrier API for this option.
|
||||
order-send-hint = When the goods are ready, send this order to the carrier.
|
||||
order-send-to-carrier = Send to
|
||||
order-send-confirm = Send this order to the carrier now?
|
||||
|
||||
# --- storefront chrome: top bar, header, footer ---
|
||||
brand-subtitle = medical supplies
|
||||
top-contact = Contact
|
||||
top-sitemap = Sitemap
|
||||
search-button = Search
|
||||
welcome = Welcome
|
||||
cart-units = items
|
||||
hotline = +421 903 410 476
|
||||
footer-tagline = Medical supplies for clinics, hospitals and home care. Delivery within 24 hours.
|
||||
footer-info = Information
|
||||
footer-account = Account
|
||||
footer-contact = Contact
|
||||
footer-terms = Terms and conditions
|
||||
footer-about = About our company
|
||||
footer-stores = Our stores
|
||||
footer-shipping = Shipping and payment
|
||||
footer-orders = My orders
|
||||
footer-email = info@kompress.sk
|
||||
footer-hours = Mon–Fri 8:00–16:00
|
||||
footer-rights = © 2026 Kompress · Medical supplies
|
||||
|
||||
@@ -500,3 +500,24 @@ order-manual-fulfillment = Manuálne spracovanie — táto možnosť nemá API d
|
||||
order-send-hint = Keď je tovar pripravený, odošlite objednávku dopravcovi.
|
||||
order-send-to-carrier = Odoslať dopravcovi
|
||||
order-send-confirm = Odoslať túto objednávku dopravcovi teraz?
|
||||
|
||||
# --- storefront chrome: top bar, header, footer ---
|
||||
brand-subtitle = zdravotnícke potreby
|
||||
top-contact = Kontakt
|
||||
top-sitemap = Mapa stránky
|
||||
search-button = Hľadať
|
||||
welcome = Vitajte
|
||||
cart-units = ks
|
||||
hotline = +421 903 410 476
|
||||
footer-tagline = Zdravotnícke potreby pre ambulancie, nemocnice a domácu starostlivosť. Dodanie do 24 hodín.
|
||||
footer-info = Informácie
|
||||
footer-account = Účet
|
||||
footer-contact = Kontakt
|
||||
footer-terms = Obchodné podmienky
|
||||
footer-about = O našej spoločnosti
|
||||
footer-stores = Naše obchody
|
||||
footer-shipping = Doprava a platba
|
||||
footer-orders = Moje objednávky
|
||||
footer-email = info@kompress.sk
|
||||
footer-hours = Po–Pia 8:00–16:00
|
||||
footer-rights = © 2026 Kompress · Zdravotnícke potreby
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -73,39 +73,56 @@
|
||||
x-data="{ cats: false, lg: window.matchMedia('(min-width: 1024px)').matches }"
|
||||
x-init="window.matchMedia('(min-width: 1024px)').addEventListener('change', e => lg = e.matches)"
|
||||
class="min-h-screen bg-surface text-on-surface antialiased dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
<!-- top utility bar (Kompress design): primary nav on the left, contact /
|
||||
sitemap links on the right. Non-sticky — it scrolls away above the
|
||||
sticky header. -->
|
||||
<div class="hidden border-b border-outline bg-surface text-xs sm:block dark:border-outline-dark dark:bg-surface-dark">
|
||||
<div class="mx-auto flex max-w-7xl items-center justify-between gap-4 px-4 py-2 text-on-surface/70 dark:text-on-surface-dark/70">
|
||||
<div class="flex items-center gap-5">
|
||||
<a href="/" data-nav="/" class="transition hover:text-primary aria-[current=page]:font-semibold aria-[current=page]:text-primary dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark">{{ t(key="nav-home", lang=lang | default(value='sk')) }}</a>
|
||||
<a href="/shop" data-nav="/shop" class="transition hover:text-primary aria-[current=page]:font-semibold aria-[current=page]:text-primary dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark">{{ t(key="nav-shop", lang=lang | default(value='sk')) }}</a>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<a href="#" class="transition hover:text-primary dark:hover:text-primary-dark">{{ t(key="top-contact", lang=lang | default(value='sk')) }}</a>
|
||||
<span class="h-3 w-px bg-outline dark:bg-outline-dark"></span>
|
||||
<a href="#" class="transition hover:text-primary dark:hover:text-primary-dark">{{ t(key="top-sitemap", lang=lang | default(value='sk')) }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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-4 px-4 py-3">
|
||||
<nav x-data="{ mobile: false }" 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) }}
|
||||
<a href="/"
|
||||
class="text-lg font-bold tracking-tight text-on-surface-strong dark:text-on-surface-dark-strong">
|
||||
{{ t(key="brand", lang=lang | default(value='sk')) }}
|
||||
|
||||
<!-- 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>
|
||||
</a>
|
||||
|
||||
<!-- desktop links — Penguin navbar link treatment via ui::nav_link -->
|
||||
<ul class="ml-2 hidden items-center gap-6 md:flex">
|
||||
<li>{{ ui::nav_link(label=t(key="nav-home", lang=lang | default(value='sk')), href="/", data_nav="/") }}</li>
|
||||
<li>{{ ui::nav_link(label=t(key="nav-shop", lang=lang | default(value='sk')), href="/shop", data_nav="/shop") }}</li>
|
||||
{% if logged_in_admin %}
|
||||
<li>{{ 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"') }}</li>
|
||||
<li>
|
||||
<form method="post" action="/logout" hx-boost="false">
|
||||
{{ ui::csrf_field() }}
|
||||
<button type="submit" class="text-sm font-medium text-danger underline-offset-2 transition hover:opacity-75 focus:outline-hidden focus-visible:underline">{{ t(key="logout", lang=lang | default(value='sk')) }}</button>
|
||||
</form>
|
||||
</li>
|
||||
{% elif logged_in_customer %}
|
||||
{# customer account links live in the profile dropdown next to the cart #}
|
||||
{% else %}
|
||||
<li>{{ ui::nav_link(label=t(key="nav-login", lang=lang | default(value='sk')), href="/login", data_nav="/login") }}</li>
|
||||
<li>{{ ui::nav_link(label=t(key="nav-register", lang=lang | default(value='sk')), href="/register", data_nav="/register") }}</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<!-- in-header search → existing GET /search (q param). Hidden on small
|
||||
screens (a compact copy lives in the mobile menu panel below). -->
|
||||
<form action="/search" method="get" role="search" class="hidden min-w-0 flex-1 md:flex md:max-w-xl">
|
||||
<div class="flex min-w-0 flex-1 overflow-hidden rounded-radius border border-outline transition focus-within:border-primary dark:border-outline-dark dark:focus-within:border-primary-dark">
|
||||
<span class="pointer-events-none flex items-center bg-surface-alt pl-3.5 text-on-surface/40 dark:bg-surface-dark-alt dark:text-on-surface-dark/40">{{ ui::icon(name="search", size="size-[18px]") }}</span>
|
||||
<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-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>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- right side: kurz + cart + settings + mobile toggle -->
|
||||
<div class="ml-auto flex items-center gap-3">
|
||||
<!-- right side: kurz + account + cart + settings + mobile toggle -->
|
||||
<div class="ml-auto flex items-center gap-2 sm:gap-3">
|
||||
<!-- exchange-rate ("kurz") display: the admin-set EUR→alt rate(s).
|
||||
Hidden when the store is EUR-only (no enabled alternatives). -->
|
||||
{% set nav_cc = currencies() %}
|
||||
@@ -117,9 +134,26 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- customer profile dropdown (avatar + name + account type) -->
|
||||
{% if logged_in_customer %}
|
||||
<!-- 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">
|
||||
{{ 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() }}
|
||||
<button type="submit" class="text-sm font-medium text-danger underline-offset-2 transition hover:opacity-75 focus:outline-hidden focus-visible:underline">{{ t(key="logout", lang=lang | default(value='sk')) }}</button>
|
||||
</form>
|
||||
</div>
|
||||
{% 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">
|
||||
<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="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>
|
||||
</a>
|
||||
{% endif %}
|
||||
<!-- cart: hover opens an Alza-style mini-cart preview (Penguin
|
||||
dropdown-with-hover), lazy-loaded from /partials/cart on each hover
|
||||
@@ -138,10 +172,16 @@
|
||||
hx-get="/partials/cart" hx-trigger="mouseenter delay:150ms" hx-target="#cart-preview-body" hx-swap="innerHTML"
|
||||
aria-label="{{ t(key='cart-title', lang=lang | default(value='sk')) }}"
|
||||
title="{{ t(key='cart-title', lang=lang | default(value='sk')) }}"
|
||||
class="relative 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 dark:text-secondary-dark dark:focus-visible:outline-secondary-dark">
|
||||
{{ ui::icon(name="cart") }}
|
||||
<span x-show="count > 0" x-cloak x-text="count"
|
||||
class="absolute -right-1 -top-1 inline-flex min-w-4 items-center justify-center rounded-full bg-primary px-1 text-[10px] font-semibold leading-4 text-on-primary dark:bg-primary-dark dark:text-on-primary-dark"></span>
|
||||
class="flex shrink-0 items-center gap-2.5 rounded-radius border border-outline bg-surface-alt px-2.5 py-1.5 text-on-surface transition hover:border-outline-strong focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary dark:border-outline-dark dark:bg-surface-dark-alt dark:text-on-surface-dark dark:hover:border-outline-dark-strong dark:focus-visible:outline-primary-dark">
|
||||
<span class="relative inline-flex text-primary dark:text-primary-dark">
|
||||
{{ ui::icon(name="cart", size="size-6") }}
|
||||
<span x-show="count > 0" x-cloak x-text="count"
|
||||
class="absolute -right-2 -top-2 inline-flex min-w-[18px] items-center justify-center rounded-full bg-danger px-1 text-[10px] font-bold leading-[18px] text-on-danger ring-2 ring-surface-alt dark:ring-surface-dark-alt"></span>
|
||||
</span>
|
||||
<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="cart-title", lang=lang | default(value='sk')) }}</span>
|
||||
<span class="text-sm font-bold text-on-surface-strong dark:text-on-surface-dark-strong"><span x-text="count">0</span> {{ t(key="cart-units", lang=lang | default(value='sk')) }}</span>
|
||||
</span>
|
||||
</a>
|
||||
<!-- hover preview panel (no id on the panel → not htmx-settled on boosted nav) -->
|
||||
<div x-cloak x-show="isOpen" x-transition
|
||||
@@ -170,6 +210,15 @@
|
||||
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">
|
||||
<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>
|
||||
<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 %}
|
||||
@@ -234,6 +283,49 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- site footer (Kompress design): brand blurb + Informácie / Účet / Kontakt
|
||||
link columns + copyright bar. Static links; reuses the nav i18n keys. -->
|
||||
<footer class="border-t border-outline bg-surface dark:border-outline-dark dark:bg-surface-dark">
|
||||
<div class="mx-auto grid max-w-7xl grid-cols-2 gap-8 px-4 py-10 md:grid-cols-4 md:px-8">
|
||||
<div class="col-span-2 md:col-span-1">
|
||||
<div class="flex items-center gap-2.5">
|
||||
<span class="inline-flex size-8 items-center justify-center rounded-radius bg-primary text-on-primary dark:bg-primary-dark dark:text-on-primary-dark">
|
||||
<svg width="17" height="17" 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="text-lg font-extrabold tracking-tight text-primary dark:text-primary-dark">{{ t(key="brand", lang=lang | default(value='sk')) }}</span>
|
||||
</div>
|
||||
<p class="mt-3 max-w-xs text-sm leading-relaxed text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="footer-tagline", lang=lang | default(value='sk')) }}</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<div class="text-xs font-bold uppercase tracking-wider text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="footer-info", lang=lang | default(value='sk')) }}</div>
|
||||
<a href="#" class="text-sm text-on-surface/70 transition hover:text-primary dark:text-on-surface-dark/70 dark:hover:text-primary-dark">{{ t(key="footer-terms", lang=lang | default(value='sk')) }}</a>
|
||||
<a href="#" class="text-sm text-on-surface/70 transition hover:text-primary dark:text-on-surface-dark/70 dark:hover:text-primary-dark">{{ t(key="footer-about", lang=lang | default(value='sk')) }}</a>
|
||||
<a href="#" class="text-sm text-on-surface/70 transition hover:text-primary dark:text-on-surface-dark/70 dark:hover:text-primary-dark">{{ t(key="footer-stores", lang=lang | default(value='sk')) }}</a>
|
||||
<a href="#" class="text-sm text-on-surface/70 transition hover:text-primary dark:text-on-surface-dark/70 dark:hover:text-primary-dark">{{ t(key="footer-shipping", lang=lang | default(value='sk')) }}</a>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<div class="text-xs font-bold uppercase tracking-wider text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="footer-account", lang=lang | default(value='sk')) }}</div>
|
||||
{% if logged_in_customer %}
|
||||
<a href="/account/orders" class="text-sm text-on-surface/70 transition hover:text-primary dark:text-on-surface-dark/70 dark:hover:text-primary-dark">{{ t(key="footer-orders", lang=lang | default(value='sk')) }}</a>
|
||||
<a href="/account/profile" class="text-sm text-on-surface/70 transition hover:text-primary dark:text-on-surface-dark/70 dark:hover:text-primary-dark">{{ t(key="nav-profile", lang=lang | default(value='sk')) }}</a>
|
||||
{% else %}
|
||||
<a href="/login" class="text-sm text-on-surface/70 transition hover:text-primary dark:text-on-surface-dark/70 dark:hover:text-primary-dark">{{ t(key="nav-login", lang=lang | default(value='sk')) }}</a>
|
||||
<a href="/register" class="text-sm text-on-surface/70 transition hover:text-primary dark:text-on-surface-dark/70 dark:hover:text-primary-dark">{{ t(key="nav-register", lang=lang | default(value='sk')) }}</a>
|
||||
{% endif %}
|
||||
<a href="/cart" hx-boost="false" class="text-sm text-on-surface/70 transition hover:text-primary dark:text-on-surface-dark/70 dark:hover:text-primary-dark">{{ t(key="cart-title", lang=lang | default(value='sk')) }}</a>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<div class="text-xs font-bold uppercase tracking-wider text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="footer-contact", lang=lang | default(value='sk')) }}</div>
|
||||
<a href="tel:+421903410476" class="text-sm text-on-surface/70 transition hover:text-primary dark:text-on-surface-dark/70 dark:hover:text-primary-dark">{{ t(key="hotline", lang=lang | default(value='sk')) }}</a>
|
||||
<a href="mailto:info@kompress.sk" class="text-sm text-on-surface/70 transition hover:text-primary dark:text-on-surface-dark/70 dark:hover:text-primary-dark">{{ t(key="footer-email", lang=lang | default(value='sk')) }}</a>
|
||||
<span class="text-sm text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="footer-hours", lang=lang | default(value='sk')) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-outline dark:border-outline-dark">
|
||||
<div class="mx-auto max-w-7xl px-4 py-4 text-xs text-on-surface/50 md:px-8 dark:text-on-surface-dark/50">{{ t(key="footer-rights", lang=lang | default(value='sk')) }}</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- toast notifications: fire from anywhere with toast('message').
|
||||
Adapted from the vendored Penguin UI component
|
||||
(penguinui-components/toast-notification/stacking-toast-notification.html):
|
||||
|
||||
176
design-parity.md
Normal file
176
design-parity.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# 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 medical‑blue accent are **done**; this file
|
||||
lists everything else from the mockup that is still missing or only half‑wired, 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 copy‑paste: lift markup out of `penguinui-components/<name>/…`
|
||||
(read‑only, never `{% include %}`'d) and adapt at the use‑site.
|
||||
|
||||
Legend: 🟢 backend already exists, only UI missing · 🟡 partial · 🔴 net‑new.
|
||||
|
||||
---
|
||||
|
||||
## A. Global chrome (`assets/views/base.html`)
|
||||
|
||||
These affect every page, so they live in `base.html`, not the home template.
|
||||
|
||||
> **✅ Implemented (pass 2)** — A1–A6 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, right‑aligned 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. In‑header search bar — ✅ done
|
||||
Mockup: full‑width 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 (two‑line) — ✅ done
|
||||
Mockup: person icon + stacked *Vitajte* / *Prihláste sa* (or the customer name when
|
||||
logged in).
|
||||
- Logged‑in/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 two‑line button. Low effort.
|
||||
|
||||
### A5. Cart button with text label — ✅ done
|
||||
Mockup: cart icon + red count badge + stacked *Košík* / *N produktov* label.
|
||||
- The icon + cookie‑driven 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: 4‑column 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.
|
||||
|
||||
### B1. Breadcrumbs — 🟢
|
||||
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.
|
||||
- Per‑category 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") — 🟢
|
||||
Mockup: styled `<select>` — Predvolené / Cena ↑ / Cena ↓ / Názov A–Z /
|
||||
Najpredávanejšie.
|
||||
- **Backend already exists**: `sort` query param (`src/controllers/shop.rs:41,81,
|
||||
179‑181`). 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/segmented‑button styling with the mockup. Cosmetic only.
|
||||
|
||||
### B5. "Porovnať" (compare) — 🔴
|
||||
Mockup: a *Porovnať* button in the listing toolbar.
|
||||
- **No compare feature exists.** Net‑new: select‑to‑compare state (Alpine + a cookie
|
||||
or server list) and a compare view. Largest item here — defer unless wanted.
|
||||
|
||||
### B6. List‑view product card — 🟡
|
||||
Mockup list rows are richer (left thumbnail, description, stock pill, price block,
|
||||
add/options/out‑of‑stock 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 — 🟡
|
||||
Mockup: top‑level 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 Alpine‑toggled
|
||||
sub‑lists (`x-data="{ open: … }"`). Active state via `aria-current` + the existing
|
||||
`markActiveNav()` pattern.
|
||||
- Medium effort.
|
||||
|
||||
### C2. "Informácie" links card — 🔴
|
||||
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`.
|
||||
Reference in New Issue
Block a user