This commit is contained in:
Priec
2026-06-24 23:04:10 +02:00
parent f665eee96e
commit a34fd1725b
5 changed files with 342 additions and 32 deletions

View File

@@ -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):