feat: Refreshing header state

This commit is contained in:
2026-05-22 14:50:24 +02:00
parent fe6682a76a
commit 76651e6012
24 changed files with 1126 additions and 735 deletions
+11
View File
@@ -0,0 +1,11 @@
# Custom patch for Colors FastCentrik theme
This patch patches homepage and header for now.
## Compilation and usage
Use `fastcentrik-template-compiler` to compile this project into single JS patch file, and inject this in the `Vlastní JavaScript` section in your Colors template configuration.
## TODO
- Slider changable time in UI
-6
View File
@@ -1,6 +0,0 @@
- Account and login in header
- Slider changable time in UI
- Fix do košíku button
+41
View File
@@ -5,3 +5,44 @@ body {
html { html {
font-size: unset !important; font-size: unset !important;
} }
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
/* Layout Variables */
--font-sans: "Geist", "Geist Fallback", ui-sans-serif, system-ui, sans-serif;
--max-w-7xl: 80rem; /* 1280px */
--h-16: 4rem; /* 64px */
--h-20: 5rem; /* 80px */
--w-10: 2.5rem; /* 40px */
--h-10: 2.5rem; /* 40px */
}
body {
background-color: var(--background);
color: var(--foreground);
}
+5 -3
View File
@@ -1,3 +1,5 @@
original_template: template_replacer:
default_styles: replace_type: "hide"
disable: true
scraper:
rerender_key: "HEADER_RERENDER"
+55
View File
@@ -0,0 +1,55 @@
document.addEventListener("DOMContentLoaded", () => {
// 1. Search Bar Toggle & Close on Outside Click
const searchBtn = document.getElementById("search-toggle");
const searchBar = document.getElementById("search-bar");
if (searchBtn && searchBar) {
searchBtn.addEventListener("click", (e) => {
e.stopPropagation();
searchBar.classList.toggle("hidden");
if (!searchBar.classList.contains("hidden")) {
searchBar.querySelector("input").focus();
}
});
// Close search if clicking outside of it
document.addEventListener("click", (e) => {
if (!searchBar.classList.contains("hidden") && !searchBar.contains(e.target) && !searchBtn.contains(e.target)) {
searchBar.classList.add("hidden");
}
});
// Prevent clicks inside search bar from closing it
searchBar.addEventListener("click", (e) => {
e.stopPropagation();
});
}
// 2. Mobile Menu Toggle
const mobileBtn = document.getElementById("mobile-menu-toggle");
const mobileMenu = document.getElementById("mobile-menu");
const iconOpen = document.getElementById("menu-icon-open");
const iconClose = document.getElementById("menu-icon-close");
if (mobileBtn && mobileMenu) {
mobileBtn.addEventListener("click", () => {
mobileMenu.classList.toggle("hidden");
iconOpen.classList.toggle("hidden");
iconClose.classList.toggle("hidden");
});
}
// 3. Mobile Sub-Menu Toggles
const submenuToggles = document.querySelectorAll(".js-submenu-toggle");
submenuToggles.forEach((toggle) => {
toggle.addEventListener("click", (e) => {
// Toggle rotation class on button
toggle.classList.toggle("active");
// Find the sibling submenu div and toggle hidden class
const submenu = toggle.parentElement.nextElementSibling;
if (submenu && submenu.classList.contains("mobile-sub-menu")) {
submenu.classList.toggle("hidden");
}
});
});
});
+63 -20
View File
@@ -1,11 +1,22 @@
(() => { ( () => {
const header = document.querySelector("#header"); const header = document.querySelector("#header");
// Extract Logo const basketInfo = document.querySelector("#basketinfo");
const basketContent = document.querySelector(".vc-basketinfoextended-content");
const itemCountBasket = parseInt(document.querySelector(".vc-basketinfoextended-numberofitems")?.textContent) || 0;
if (basketInfo && basketContent && basketContent.children.length === 0 && itemCountBasket > 0) {
const headerLink = basketInfo.querySelector('.vc-basketinfoextended-header');
if (headerLink) {
headerLink.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));
headerLink.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
}
}
const logoEl = header.querySelector(".vc-headerlogo img"); const logoEl = header.querySelector(".vc-headerlogo img");
const logo = { const logo = {
src: logoEl ? logoEl.src : '', src: logoEl ? logoEl.getAttribute("src") : '',
alt: logoEl ? logoEl.alt : '', alt: logoEl ? logoEl.getAttribute("alt") : '',
}; };
const customContentBlock = header.querySelector(".custom-content-block.custom-content-header-1 > .content"); const customContentBlock = header.querySelector(".custom-content-block.custom-content-header-1 > .content");
@@ -13,22 +24,17 @@
function extractMenuTree(ulElement) { function extractMenuTree(ulElement) {
if (!ulElement) return []; if (!ulElement) return [];
const listItems = Array.from(ulElement.children).filter(el => el.tagName === 'LI'); const listItems = Array.from(ulElement.children).filter(el => el.tagName === 'LI');
return listItems.map(li => { return listItems.map(li => {
const link = li.querySelector(":scope > a"); const link = li.querySelector(":scope > a");
const itemData = { const itemData = {
text: link ? link.textContent.trim() : '', text: link ? link.textContent.trim() : '',
href: link ? link.href : '' href: link ? link.getAttribute("href") : ''
}; };
const subMenu = li.querySelector(":scope > ul[role='menu']"); const subMenu = li.querySelector(":scope > ul[role='menu']");
if (subMenu) { if (subMenu) {
itemData.sublinks = extractMenuTree(subMenu); itemData.sublinks = extractMenuTree(subMenu);
} }
return itemData; return itemData;
}); });
} }
@@ -37,26 +43,63 @@
const menuData = extractMenuTree(rootMenu); const menuData = extractMenuTree(rootMenu);
const menuTop = header.querySelector("#menutop ul[role='menu']"); const menuTop = header.querySelector("#menutop ul[role='menu']");
const menuTopLinks = menuTop.querySelectorAll("li > a"); const menuTopData = menuTop ? Array.from(menuTop.querySelectorAll("li > a")).map(link => ({
const menuTopData = Array.from(menuTopLinks).map(link => ({
text: link.textContent.trim(), text: link.textContent.trim(),
href: link.href href: link.getAttribute("href")
})) : [];
const searchForm = header.querySelector("#frm-searchbox");
const searchAction = searchForm ? searchForm.getAttribute("action") : "/hledani";
const userMenuNodes = header.querySelectorAll("#menuuser .content ul li a");
const userMenu = Array.from(userMenuNodes).map(a => ({
text: a.textContent.trim(),
href: a.getAttribute("href")
})); }));
const itemCountBasket = parseInt(header.querySelector("#basketinfo .vc-basketinfoextended-numberofitems").textContent) || 0; const basketPrice = header.querySelector("#basketinfo .vc-basketinfoextended-price")?.textContent.trim() || '';
const basketLink = header.querySelector("#basketinfo .vc-basketinfoextended-header")?.getAttribute("href") || '/kosik';
const basket = { // Extract the newly populated items
itemCount: itemCountBasket, const basketItemsNodes = header.querySelectorAll(".vc-basketinfoextended-item");
link: header.querySelector("#basketinfo .vc-basketinfoextended-header").href || '/kosik' const basketItems = Array.from(basketItemsNodes).map(item => {
} const img = item.querySelector("img");
const link = item.querySelector(".vc-basketinfoextended-item-namewrapper p a");
const qty = item.querySelector(".quantity");
const price = item.querySelector(".vc-basketinfoextended-item-price");
const remove = item.querySelector(".removeBasketItemLink");
return { return {
imgSrc: img ? (img.getAttribute("data-src") || img.getAttribute("src")) : "",
imgAlt: img ? img.getAttribute("alt") : "",
name: link ? link.textContent.trim() : "",
href: link ? link.getAttribute("href") : "",
qty: qty ? qty.textContent.trim() : "",
price: price ? price.textContent.trim() : "",
removeLink: remove ? remove.getAttribute("href") : ""
};
});
const freeDeliveryNode = header.querySelector(".ndBasketFreeDelivery_basketpopup p");
const freeDeliveryText = freeDeliveryNode ? freeDeliveryNode.textContent.trim() : "";
const finalData = {
logo, logo,
customContentHeader, customContentHeader,
searchAction,
userMenu,
menu: { menu: {
top: menuTopData, top: menuTopData,
main: menuData main: menuData
}, },
basket, basket: {
itemCount: itemCountBasket,
price: basketPrice,
link: basketLink,
items: basketItems,
freeDelivery: freeDeliveryText
}
}; };
return finalData;
})(); })();
+523
View File
@@ -0,0 +1,523 @@
.site-header {
font-family: var(--font-sans);
position: sticky;
top: 0;
z-index: 50;
background-color: var(--card);
border-bottom: 1px solid var(--border);
color: var(--foreground);
}
.site-header a,
.site-header button {
text-decoration: none;
color: inherit;
font-family: inherit;
}
.container {
max-width: var(--max-w-7xl);
margin: 0 auto;
padding: 0 1rem;
}
@media (min-width: 640px) {
.container {
padding: 0 1.5rem;
}
}
@media (min-width: 1024px) {
.container {
padding: 0 2rem;
}
}
/* Utilities */
.text-xs { font-size: 0.75rem; line-height: 1rem; }
.text-sm { font-size: 0.875rem; line-height: 1.25rem; }
.text-base { font-size: 1rem; line-height: 1.5rem; }
.text-lg { font-size: 1.125rem; line-height: 1.75rem; }
.text-xl { font-size: 1.25rem; line-height: 1.75rem; }
.font-medium { font-weight: 500; }
.font-bold { font-weight: 700; }
.uppercase { text-transform: uppercase; }
.tracking-wide { letter-spacing: 0.025em; }
.tracking-tight { letter-spacing: -0.025em; }
.hidden { display: none !important; }
/* ------------------ Top Bar ------------------ */
.top-bar {
background-color: var(--primary);
color: var(--primary-foreground);
padding: 0.5rem 0;
}
.top-bar-inner {
display: flex;
align-items: center;
justify-content: space-between;
}
.top-bar-left,
.top-bar-right {
display: flex;
align-items: center;
gap: 1.5rem;
}
.top-bar-left a,
.top-bar-right a {
transition: opacity 0.2s;
}
.top-bar-left a:hover,
.top-bar-right a:hover {
opacity: 0.8;
}
.top-bar-left { display: none; }
@media (min-width: 640px) {
.top-bar-left { display: flex; }
}
/* ------------------ Main Header ------------------ */
.main-header {
display: flex;
align-items: center;
justify-content: space-between;
height: var(--h-16);
}
@media (min-width: 1024px) {
.main-header { height: var(--h-20); }
}
/* Logo */
.logo {
display: flex;
align-items: center;
gap: 0.5rem;
}
.logo-img {
height: var(--h-10);
width: auto;
}
.logo-icon {
width: var(--w-10);
height: var(--h-10);
border-radius: 9999px;
background: linear-gradient(to bottom right, var(--accent), color-mix(in srgb, var(--accent) 70%, transparent));
display: flex;
align-items: center;
justify-content: center;
color: var(--accent-foreground);
}
.logo-text { color: var(--foreground); }
/* ------------------ Desktop Nav ------------------ */
.desktop-nav {
display: none;
align-items: center;
gap: 2rem;
height: 100%;
}
@media (min-width: 1024px) {
.desktop-nav { display: flex; }
}
.nav-item {
position: relative;
height: 100%;
display: flex;
align-items: center;
}
.nav-link {
display: flex;
align-items: center;
gap: 0.25rem;
padding: 0.5rem 0;
color: var(--foreground);
transition: color 0.2s;
}
.nav-link:hover { color: var(--accent-foreground); }
.nav-icon { width: 1rem; height: 1rem; transition: transform 0.2s; }
.nav-item:hover > .nav-link .nav-icon { transform: rotate(180deg); }
/* Dropdowns */
.dropdown {
display: none;
position: absolute;
top: 100%;
left: 0;
padding-top: 0.5rem;
z-index: 50;
}
.nav-item:hover .dropdown { display: block; }
.dropdown-inner {
background-color: var(--card);
border: 1px solid var(--border);
border-radius: 0.5rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
min-width: 200px;
padding: 0.5rem 0;
}
.dropdown-item { position: relative; }
.dropdown-link {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.625rem 1rem;
color: var(--foreground);
transition: all 0.2s;
}
.dropdown-link:hover,
.dropdown-item:hover > .dropdown-link {
background-color: var(--secondary);
color: var(--accent-foreground);
}
/* Sub-Dropdowns */
.sub-dropdown {
display: none;
position: absolute;
top: 0;
left: 100%;
padding-left: 0.5rem;
z-index: 50;
}
.dropdown-item:hover .sub-dropdown { display: block; }
/* ------------------ Actions ------------------ */
.actions {
display: flex;
align-items: center;
gap: 1rem;
}
.action-btn {
padding: 0.5rem;
border-radius: 9999px;
border: none;
background: transparent;
color: var(--foreground);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.2s;
}
.action-btn:hover { background-color: var(--secondary); }
.action-btn svg { width: 1.25rem; height: 1.25rem; }
.desktop-user { display: none; }
@media (min-width: 640px) {
.desktop-user { display: flex; }
}
.mobile-menu-btn { display: flex; }
@media (min-width: 1024px) {
.mobile-menu-btn { display: none; }
}
.cart-wrapper { position: relative; }
.cart-badge {
position: absolute;
top: -0.25rem;
right: -0.25rem;
width: 1.25rem;
height: 1.25rem;
background-color: var(--accent);
color: var(--accent-foreground);
border-radius: 9999px;
display: flex;
align-items: center;
justify-content: center;
}
/* ------------- ACTION DROPDOWNS ------------- */
.action-dropdown-wrapper {
position: relative;
height: auto;
}
.action-dropdown {
position: absolute !important;
top: 100% !important;
right: 0 !important;
left: auto !important;
padding-top: 0.5rem;
}
.action-dropdown::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 0.5rem;
}
.user-dropdown .dropdown-inner {
min-width: 180px;
}
/* ------------- BASKET REDESIGN ------------- */
.cart-dropdown .dropdown-inner {
width: 22rem;
max-width: calc(100vw - 2rem);
padding: 0;
cursor: default;
overflow: hidden;
}
.basket-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.25rem;
border-bottom: 1px solid var(--border);
background-color: color-mix(in srgb, var(--secondary) 50%, transparent);
}
.basket-title {
font-weight: 700;
font-size: 1rem;
color: var(--foreground);
}
.basket-count-text {
font-size: 0.8125rem;
color: var(--muted-foreground);
font-weight: 500;
}
.basket-items-scroll {
max-height: 55vh;
overflow-y: auto;
padding: 0.5rem 0;
}
.basket-item {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.75rem 1.25rem;
transition: background-color 0.2s;
}
.basket-item:hover {
background-color: var(--secondary);
}
.basket-item-img-wrap {
width: 3.5rem;
height: 3.5rem;
flex-shrink: 0;
border-radius: 0.5rem;
border: 1px solid var(--border);
overflow: hidden;
background-color: var(--card);
}
.basket-item-img-wrap img {
width: 100%;
height: 100%;
object-fit: cover;
}
.basket-item-details {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.basket-item-name {
font-size: 0.875rem;
font-weight: 600;
color: var(--foreground);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-decoration: none;
}
.basket-item-name:hover { color: var(--accent); }
.basket-item-price-row {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
}
.basket-item-qty { color: var(--muted-foreground); }
.basket-item-price { font-weight: 700; color: var(--foreground); }
.basket-item-remove {
color: var(--muted-foreground);
padding: 0.5rem;
margin: -0.5rem;
border-radius: 0.375rem;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
}
.basket-item-remove svg { width: 1.25rem; height: 1.25rem; }
.basket-item-remove:hover {
color: #ef4444;
background-color: color-mix(in srgb, #ef4444 10%, transparent);
}
.basket-empty {
padding: 3rem 1.5rem;
text-align: center;
color: var(--muted-foreground);
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
.basket-empty-icon {
width: 3rem;
height: 3rem;
opacity: 0.5;
}
.basket-footer {
padding: 1.25rem;
border-top: 1px solid var(--border);
background-color: color-mix(in srgb, var(--secondary) 50%, transparent);
display: flex;
flex-direction: column;
gap: 1rem;
}
.basket-freedelivery {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
font-size: 0.8125rem;
font-weight: 600;
color: #10b981;
background-color: color-mix(in srgb, #10b981 10%, transparent);
padding: 0.5rem;
border-radius: 0.5rem;
text-align: center;
}
.basket-freedelivery svg { width: 1rem; height: 1rem; }
.basket-total {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 1rem;
font-weight: 500;
color: var(--foreground);
}
.basket-total-price {
font-size: 1.25rem;
font-weight: 700;
color: var(--accent);
}
.basket-checkout-btn {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
background-color: var(--primary);
color: var(--primary-foreground);
padding: 0.75rem;
border-radius: 0.5rem;
font-size: 0.9375rem;
font-weight: 600;
text-decoration: none;
transition: background-color 0.2s;
}
.basket-checkout-btn:hover {
background-color: color-mix(in srgb, var(--primary) 90%, transparent);
}
/* ------------------ Search Bar ------------------ */
.search-container {
padding-bottom: 1rem;
animation: slideDown 0.2s ease-out;
}
.search-input-wrapper {
position: relative;
display: flex;
width: 100%;
}
.search-input {
width: 100%;
padding: 0.75rem 3rem 0.75rem 1rem;
background-color: var(--secondary);
border: 1px solid transparent;
border-radius: 0.5rem;
color: var(--foreground);
outline: none;
transition: all 0.2s;
}
.search-input::placeholder { color: var(--muted-foreground); }
.search-input:focus { box-shadow: 0 0 0 2px var(--ring); }
.search-submit-btn {
position: absolute;
right: 0.5rem;
top: 50%;
transform: translateY(-50%);
background: transparent;
border: none;
cursor: pointer;
padding: 0.25rem;
color: var(--muted-foreground);
border-radius: 0.25rem;
}
.search-submit-btn:hover { color: var(--foreground); }
.search-submit-btn svg { width: 1.25rem; height: 1.25rem; }
/* ------------------ Mobile Menu ------------------ */
.mobile-menu {
border-top: 1px solid var(--border);
animation: slideDown 0.2s ease-out;
}
@media (min-width: 1024px) {
.mobile-menu { display: none !important; }
}
.mobile-nav-inner {
padding: 1rem 0;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.mobile-item-wrap { position: relative; }
.mobile-item-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.mobile-link {
flex: 1;
padding: 0.5rem 0;
color: var(--foreground);
transition: color 0.2s;
}
.mobile-link:hover { color: var(--accent-foreground); }
.mobile-toggle {
padding: 0.5rem;
border: none;
background: transparent;
border-radius: 9999px;
color: var(--foreground);
}
.mobile-toggle:hover { background-color: var(--secondary); }
.mobile-toggle svg { width: 1rem; height: 1rem; transition: transform 0.2s; }
.mobile-toggle.active svg { transform: rotate(180deg); }
.mobile-sub-menu {
border-left: 2px solid var(--border);
margin-left: 0.5rem;
padding-left: 1rem;
}
.mobile-contact {
padding-top: 1rem;
margin-top: 0.5rem;
border-top: 1px solid var(--border);
color: var(--muted-foreground);
font-size: 0.875rem;
}
@media (min-width: 640px) {
.mobile-contact { display: none; }
}
a {
color: unset !important;
}
+113 -680
View File
@@ -1,5 +1,4 @@
<header class="site-header"> <header class="site-header">
<!-- Top bar (bg-primary) -->
<div class="top-bar text-sm"> <div class="top-bar text-sm">
<div class="container top-bar-inner"> <div class="container top-bar-inner">
<div class="top-bar-left">{{{customContentHeader}}}</div> <div class="top-bar-left">{{{customContentHeader}}}</div>
@@ -11,70 +10,48 @@
</div> </div>
</div> </div>
<!-- Main Header -->
<div class="container"> <div class="container">
<div class="main-header"> <div class="main-header">
<!-- Logo -->
<a href="/" class="logo"> <a href="/" class="logo">
{{#if logo.src}} {{#if logo.src}}
<img src="{{logo.src}}" alt="{{logo.alt}}" class="logo-img" /> <img src="{{logo.src}}" alt="{{logo.alt}}" class="logo-img" />
{{else}} {{else}}
<div class="logo-icon text-lg font-bold">C</div> <div class="logo-icon text-lg font-bold">C</div>
<span class="logo-text text-xl font-bold tracking-tight">colors</span> <span class="logo-text text-xl font-bold tracking-tight">colors</span>
{{/if}} {{/if}}
</a> </a>
<!-- Desktop Navigation -->
<nav class="desktop-nav"> <nav class="desktop-nav">
{{#each menu.main}} {{#each menu.main}}
<div class="nav-item text-sm font-medium uppercase tracking-wide"> <div class="nav-item text-sm font-medium uppercase tracking-wide">
<a href="{{this.href}}" class="nav-link"> <a href="{{this.href}}" class="nav-link">
{{this.text}} {{#if this.sublinks}} {{this.text}}
<svg {{#if this.sublinks}}
xmlns="http://www.w3.org/2000/svg" <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="nav-icon">
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="nav-icon"
>
<path d="m6 9 6 6 6-6" /> <path d="m6 9 6 6 6-6" />
</svg> </svg>
{{/if}} {{/if}}
</a> </a>
<!-- First level dropdown -->
{{#if this.sublinks}} {{#if this.sublinks}}
<div class="dropdown"> <div class="dropdown">
<div class="dropdown-inner"> <div class="dropdown-inner">
{{#each this.sublinks}} {{#each this.sublinks}}
<div class="dropdown-item"> <div class="dropdown-item">
<a href="{{this.href}}" class="dropdown-link text-sm"> <a href="{{this.href}}" class="dropdown-link text-sm">
{{this.text}} {{#if this.sublinks}} {{this.text}}
<!-- Removed nav-icon class so it no longer accidentally flips on top-level hover --> {{#if this.sublinks}}
<svg <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 1rem; height: 1rem">
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
style="width: 1rem; height: 1rem"
>
<path d="m9 18 6-6-6-6" /> <path d="m9 18 6-6-6-6" />
</svg> </svg>
{{/if}} {{/if}}
</a> </a>
<!-- Second level dropdown -->
{{#if this.sublinks}} {{#if this.sublinks}}
<div class="sub-dropdown"> <div class="sub-dropdown">
<div class="dropdown-inner"> <div class="dropdown-inner">
{{#each this.sublinks}} {{#each this.sublinks}}
<!-- Applying dropdown-link here fixes the hover issue -->
<a href="{{this.href}}" class="dropdown-link text-sm"> <a href="{{this.href}}" class="dropdown-link text-sm">
{{this.text}} {{this.text}}
</a> </a>
@@ -91,117 +68,108 @@
{{/each}} {{/each}}
</nav> </nav>
<!-- Actions -->
<div class="actions"> <div class="actions">
<button id="search-toggle" class="action-btn" aria-label="Vyhledávání"> <button id="search-toggle" class="action-btn" aria-label="Vyhledávání">
<svg <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.3-4.3" />
</svg>
</button> </button>
<a href="#" class="action-btn desktop-user">
<svg <div class="nav-item action-dropdown-wrapper desktop-user">
xmlns="http://www.w3.org/2000/svg" <a href="/prihlaseni" class="action-btn">
viewBox="0 0 24 24" <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
fill="none" </a>
stroke="currentColor" {{#if userMenu}}
stroke-width="2" <div class="dropdown action-dropdown user-dropdown">
stroke-linecap="round" <div class="dropdown-inner">
stroke-linejoin="round" {{#each userMenu}}
> <a href="{{this.href}}" class="dropdown-link text-sm font-medium">{{this.text}}</a>
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /> {{/each}}
<circle cx="12" cy="7" r="4" /> </div>
</svg> </div>
</a> {{/if}}
<a href="{{basket.link}}" class="action-btn cart-wrapper"> </div>
<svg
xmlns="http://www.w3.org/2000/svg" <div class="nav-item action-dropdown-wrapper cart-wrapper">
viewBox="0 0 24 24" <a href="{{basket.link}}" class="action-btn">
fill="none" <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="21" r="1" /><circle cx="19" cy="21" r="1" /><path d="M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12" /></svg>
stroke="currentColor" <span class="cart-badge text-xs font-bold">{{basket.itemCount}}</span>
stroke-width="2" </a>
stroke-linecap="round"
stroke-linejoin="round" <div class="dropdown action-dropdown cart-dropdown">
> <div class="dropdown-inner basket-inner">
<circle cx="8" cy="21" r="1" />
<circle cx="19" cy="21" r="1" /> <div class="basket-header">
<path <span class="basket-title">Váš košík</span>
d="M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12" <span class="basket-count-text">{{basket.itemCount}} položek</span>
/> </div>
</svg>
<span class="cart-badge text-xs font-bold">{{basket.itemCount}}</span> {{#if basket.items}}
</a> <div class="basket-items-scroll">
<button {{#each basket.items}}
id="mobile-menu-toggle" <div class="basket-item">
class="action-btn mobile-menu-btn" <div class="basket-item-img-wrap">
aria-label="Menu" {{#if this.imgSrc}}
> <img src="{{this.imgSrc}}" alt="{{this.imgAlt}}" />
<svg {{else}}
id="menu-icon-open" <div class="basket-img-placeholder"></div>
xmlns="http://www.w3.org/2000/svg" {{/if}}
viewBox="0 0 24 24" </div>
fill="none" <div class="basket-item-details">
stroke="currentColor" <a href="{{this.href}}" class="basket-item-name">{{this.name}}</a>
stroke-width="2" <div class="basket-item-price-row">
stroke-linecap="round" <span class="basket-item-qty">{{this.qty}}</span>
stroke-linejoin="round" <span class="basket-item-price">{{this.price}}</span>
> </div>
<line x1="4" x2="20" y1="12" y2="12" /> </div>
<line x1="4" x2="20" y1="6" y2="6" /> <a href="{{this.removeLink}}" class="basket-item-remove" title="Odstranit">
<line x1="4" x2="20" y1="18" y2="18" /> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/></svg>
</svg> </a>
<svg </div>
id="menu-icon-close" {{/each}}
class="hidden" </div>
xmlns="http://www.w3.org/2000/svg" {{else}}
viewBox="0 0 24 24" <div class="basket-empty">
fill="none" <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="basket-empty-icon"><circle cx="8" cy="21" r="1"/><circle cx="19" cy="21" r="1"/><path d="M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12"/></svg>
stroke="currentColor" <p>Váš košík je zatím prázdný</p>
stroke-width="2" </div>
stroke-linecap="round" {{/if}}
stroke-linejoin="round"
> <div class="basket-footer">
<line x1="18" y1="6" x2="6" y2="18"></line> {{#if basket.freeDelivery}}
<line x1="6" y1="6" x2="18" y2="18"></line> <div class="basket-freedelivery">
</svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><path d="m9 12 2 2 4-4"/></svg>
{{basket.freeDelivery}}
</div>
{{/if}}
<div class="basket-total">
<span>Celkem:</span>
<span class="basket-total-price">{{basket.price}}</span>
</div>
<a href="{{basket.link}}" class="basket-checkout-btn">Přejít do košíku</a>
</div>
</div>
</div>
</div>
<button id="mobile-menu-toggle" class="action-btn mobile-menu-btn" aria-label="Menu">
<svg id="menu-icon-open" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" x2="20" y1="12" y2="12" /><line x1="4" x2="20" y1="6" y2="6" /><line x1="4" x2="20" y1="18" y2="18" /></svg>
<svg id="menu-icon-close" class="hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button> </button>
</div> </div>
</div> </div>
<!-- Search Bar (Slide down) -->
<div id="search-bar" class="search-container hidden"> <div id="search-bar" class="search-container hidden">
<div class="search-input-wrapper"> <form action="{{#if searchAction}}{{searchAction}}{{else}}/hledani{{/if}}" method="GET" class="search-input-wrapper">
<input <input type="search" name="query" placeholder="Zadejte hledaný výraz..." class="search-input" required />
type="search" <button type="submit" class="search-icon search-submit-btn" aria-label="Hledat">
placeholder="Hledat produkty..." <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
class="search-input" </button>
/> </form>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="search-icon"
>
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.3-4.3" />
</svg>
</div>
</div> </div>
</div> </div>
<!-- Mobile Menu (Slide down) -->
<div id="mobile-menu" class="mobile-menu hidden"> <div id="mobile-menu" class="mobile-menu hidden">
<nav class="container mobile-nav-inner"> <nav class="container mobile-nav-inner">
{{#each menu.main}} {{#each menu.main}}
@@ -212,17 +180,7 @@
</a> </a>
{{#if this.sublinks}} {{#if this.sublinks}}
<button class="mobile-toggle js-submenu-toggle"> <button class="mobile-toggle js-submenu-toggle">
<svg <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></svg>
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m6 9 6 6 6-6" />
</svg>
</button> </button>
{{/if}} {{/if}}
</div> </div>
@@ -237,23 +195,10 @@
</a> </a>
</div> </div>
{{#if this.sublinks}} {{#if this.sublinks}}
<!-- Auto-expand 3rd level on mobile to match React setup structure --> <div class="mobile-sub-menu" style="display: block; border-color: transparent; margin-top: 0; padding-top: 0;">
<div
class="mobile-sub-menu"
style="
display: block;
border-color: transparent;
margin-top: 0;
padding-top: 0;
"
>
{{#each this.sublinks}} {{#each this.sublinks}}
<div class="mobile-item-header"> <div class="mobile-item-header">
<a <a href="{{this.href}}" class="mobile-link text-sm" style="color: var(--muted-foreground)">
href="{{this.href}}"
class="mobile-link text-sm"
style="color: var(--muted-foreground)"
>
{{this.text}} {{this.text}}
</a> </a>
</div> </div>
@@ -266,527 +211,15 @@
{{/if}} {{/if}}
</div> </div>
{{/each}} {{/each}}
<!-- Top bar data fallback on mobile --> <div class="mobile-contact">
<div class="mobile-contact">{{{customContentHeader}}}</div> {{{customContentHeader}}}
<div style="margin-top: 0.5rem; display:flex; gap:1rem;">
{{#each userMenu}}
<a href="{{this.href}}" style="color: var(--accent);">{{this.text}}</a>
{{/each}}
</div>
</div>
</nav> </nav>
</div> </div>
<style>
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&display=swap");
:root {
/* Exact provided variables (Light Mode Only) */
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
/* Layout Variables */
--font-sans:
"Geist", "Geist Fallback", ui-sans-serif, system-ui, sans-serif;
--max-w-7xl: 80rem; /* 1280px */
--h-16: 4rem; /* 64px */
--h-20: 5rem; /* 80px */
--w-10: 2.5rem; /* 40px */
--h-10: 2.5rem; /* 40px */
}
/* Reset & Base Setup */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--background);
color: var(--foreground);
}
.site-header {
font-family: var(--font-sans);
position: sticky;
top: 0;
z-index: 50;
background-color: var(--card);
border-bottom: 1px solid var(--border);
color: var(--foreground);
}
.site-header a,
.site-header button {
text-decoration: none;
color: inherit;
font-family: inherit;
}
/* Container bounds mapping exactly to max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 */
.container {
max-width: var(--max-w-7xl);
margin: 0 auto;
padding: 0 1rem;
}
@media (min-width: 640px) {
.container {
padding: 0 1.5rem;
}
}
@media (min-width: 1024px) {
.container {
padding: 0 2rem;
}
}
/* Utilities */
.text-xs {
font-size: 0.75rem;
line-height: 1rem;
}
.text-sm {
font-size: 0.875rem;
line-height: 1.25rem;
}
.text-base {
font-size: 1rem;
line-height: 1.5rem;
}
.text-lg {
font-size: 1.125rem;
line-height: 1.75rem;
}
.text-xl {
font-size: 1.25rem;
line-height: 1.75rem;
}
.font-medium {
font-weight: 500;
}
.font-bold {
font-weight: 700;
}
.uppercase {
text-transform: uppercase;
}
.tracking-wide {
letter-spacing: 0.025em;
}
.tracking-tight {
letter-spacing: -0.025em;
}
.hidden {
display: none !important;
}
/* ------------------ Top Bar ------------------ */
.top-bar {
background-color: var(--primary);
color: var(--primary-foreground);
padding: 0.5rem 0; /* py-2 */
}
.top-bar-inner {
display: flex;
align-items: center;
justify-content: space-between;
}
.top-bar-left,
.top-bar-right {
display: flex;
align-items: center;
gap: 1.5rem; /* gap-6 */
}
.top-bar-left a,
.top-bar-right a {
transition: opacity 0.2s;
}
.top-bar-left a:hover,
.top-bar-right a:hover {
opacity: 0.8;
}
.top-bar-left {
display: none;
}
@media (min-width: 640px) {
.top-bar-left {
display: flex;
}
}
/* ------------------ Main Header ------------------ */
.main-header {
display: flex;
align-items: center;
justify-content: space-between;
height: var(--h-16);
}
@media (min-width: 1024px) {
.main-header {
height: var(--h-20);
}
}
/* Logo */
.logo {
display: flex;
align-items: center;
gap: 0.5rem; /* gap-2 */
}
.logo-img {
height: var(--h-10);
width: auto;
}
.logo-icon {
width: var(--w-10);
height: var(--h-10);
border-radius: 9999px;
background: linear-gradient(
to bottom right,
var(--accent),
color-mix(in srgb, var(--accent) 70%, transparent)
);
display: flex;
align-items: center;
justify-content: center;
color: var(--accent-foreground);
}
.logo-text {
color: var(--foreground);
}
/* ------------------ Desktop Nav ------------------ */
.desktop-nav {
display: none;
align-items: center;
gap: 2rem; /* gap-8 */
height: 100%;
}
@media (min-width: 1024px) {
.desktop-nav {
display: flex;
}
}
.nav-item {
position: relative;
height: 100%;
display: flex;
align-items: center;
}
.nav-link {
display: flex;
align-items: center;
gap: 0.25rem; /* gap-1 */
padding: 0.5rem 0; /* py-2 */
color: var(--foreground);
transition: color 0.2s;
}
.nav-link:hover {
color: var(--accent-foreground);
}
.nav-icon {
width: 1rem;
height: 1rem; /* w-4 h-4 */
transition: transform 0.2s;
}
/* ONLY rotate the chevron inside the top-level link, not the nested ones */
.nav-item:hover > .nav-link .nav-icon {
transform: rotate(180deg);
}
/* Dropdowns */
.dropdown {
display: none;
position: absolute;
top: 100%;
left: 0;
padding-top: 0.5rem; /* pt-2 */
z-index: 50;
}
.nav-item:hover .dropdown {
display: block;
}
.dropdown-inner {
background-color: var(--card);
border: 1px solid var(--border);
border-radius: 0.5rem;
box-shadow:
0 10px 15px -3px rgba(0, 0, 0, 0.1),
0 4px 6px -4px rgba(0, 0, 0, 0.1);
min-width: 200px;
padding: 0.5rem 0; /* py-2 */
}
.dropdown-item {
position: relative;
}
.dropdown-link {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.625rem 1rem; /* px-4 py-2.5 */
color: var(--foreground);
transition: all 0.2s;
}
/* Apply hover effect directly to the link, and keep it active when hovering a child dropdown */
.dropdown-link:hover,
.dropdown-item:hover > .dropdown-link {
background-color: var(--secondary);
color: var(--accent-foreground);
}
/* Sub-Dropdowns */
.sub-dropdown {
display: none;
position: absolute;
top: 0;
left: 100%;
padding-left: 0.5rem; /* pl-2 */
z-index: 50;
}
.dropdown-item:hover .sub-dropdown {
display: block;
}
/* ------------------ Actions ------------------ */
.actions {
display: flex;
align-items: center;
gap: 1rem; /* gap-4 */
}
.action-btn {
padding: 0.5rem; /* p-2 */
border-radius: 9999px;
border: none;
background: transparent;
color: var(--foreground);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.2s;
}
.action-btn:hover {
background-color: var(--secondary);
}
.action-btn svg {
width: 1.25rem;
height: 1.25rem; /* w-5 h-5 */
}
.desktop-user {
display: none;
}
@media (min-width: 640px) {
.desktop-user {
display: flex;
}
}
.mobile-menu-btn {
display: flex;
}
@media (min-width: 1024px) {
.mobile-menu-btn {
display: none;
}
}
.cart-wrapper {
position: relative;
}
.cart-badge {
position: absolute;
top: -0.25rem;
right: -0.25rem; /* -top-1 -right-1 */
width: 1.25rem;
height: 1.25rem; /* w-5 h-5 */
background-color: var(--accent);
color: var(--accent-foreground);
border-radius: 9999px;
display: flex;
align-items: center;
justify-content: center;
}
/* ------------------ Search Bar ------------------ */
.search-container {
padding-bottom: 1rem; /* pb-4 */
animation: slideDown 0.2s ease-out;
}
.search-input-wrapper {
position: relative;
}
.search-input {
width: 100%;
padding: 0.75rem 1rem 0.75rem 3rem; /* px-4 py-3 pl-12 */
background-color: var(--secondary);
border: 1px solid transparent;
border-radius: 0.5rem; /* rounded-lg */
color: var(--foreground);
outline: none;
transition: all 0.2s;
}
.search-input::placeholder {
color: var(--muted-foreground);
}
.search-input:focus {
box-shadow: 0 0 0 2px var(--ring);
}
.search-icon {
position: absolute;
left: 1rem;
top: 50%;
transform: translateY(-50%);
color: var(--muted-foreground);
width: 1.25rem;
height: 1.25rem;
}
/* ------------------ Mobile Menu ------------------ */
.mobile-menu {
border-top: 1px solid var(--border);
animation: slideDown 0.2s ease-out;
}
@media (min-width: 1024px) {
.mobile-menu {
display: none !important;
}
}
.mobile-nav-inner {
padding: 1rem 0; /* py-4 */
display: flex;
flex-direction: column;
gap: 0.25rem; /* space-y-1 */
}
.mobile-item-wrap {
position: relative;
}
.mobile-item-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.mobile-link {
flex: 1;
padding: 0.5rem 0; /* py-2 */
color: var(--foreground);
transition: color 0.2s;
}
.mobile-link:hover {
color: var(--accent-foreground);
}
.mobile-toggle {
padding: 0.5rem;
border: none;
background: transparent;
border-radius: 9999px;
color: var(--foreground);
}
.mobile-toggle:hover {
background-color: var(--secondary);
}
.mobile-toggle svg {
width: 1rem;
height: 1rem;
transition: transform 0.2s;
}
.mobile-toggle.active svg {
transform: rotate(180deg);
}
.mobile-sub-menu {
border-left: 2px solid var(--border);
margin-left: 0.5rem; /* ml-2 */
padding-left: 1rem; /* pl-4 */
}
.mobile-contact {
padding-top: 1rem; /* pt-4 */
margin-top: 0.5rem;
border-top: 1px solid var(--border);
color: var(--muted-foreground);
font-size: 0.875rem;
}
@media (min-width: 640px) {
.mobile-contact {
display: none;
}
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-0.5rem);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>
<script>
document.addEventListener("DOMContentLoaded", () => {
// 1. Search Bar Toggle
const searchBtn = document.getElementById("search-toggle");
const searchBar = document.getElementById("search-bar");
if (searchBtn && searchBar) {
searchBtn.addEventListener("click", () => {
searchBar.classList.toggle("hidden");
if (!searchBar.classList.contains("hidden")) {
searchBar.querySelector("input").focus();
}
});
}
// 2. Mobile Menu Toggle
const mobileBtn = document.getElementById("mobile-menu-toggle");
const mobileMenu = document.getElementById("mobile-menu");
const iconOpen = document.getElementById("menu-icon-open");
const iconClose = document.getElementById("menu-icon-close");
if (mobileBtn && mobileMenu) {
mobileBtn.addEventListener("click", () => {
mobileMenu.classList.toggle("hidden");
iconOpen.classList.toggle("hidden");
iconClose.classList.toggle("hidden");
});
}
// 3. Mobile Sub-Menu Toggles
const submenuToggles = document.querySelectorAll(".js-submenu-toggle");
submenuToggles.forEach((toggle) => {
toggle.addEventListener("click", (e) => {
// Toggle rotation class on button
toggle.classList.toggle("active");
// Find the sibling submenu div and toggle hidden class
const submenu = toggle.parentElement.nextElementSibling;
if (submenu && submenu.classList.contains("mobile-sub-menu")) {
submenu.classList.toggle("hidden");
}
});
});
});
</script>
</header> </header>
+6 -2
View File
@@ -1,6 +1,10 @@
#AjaxMainSection { #AjaxMainSection,
.section_middle > .container > .inner {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1.5rem; gap: 4.5rem;
}
#AjaxMainSection {
padding-top: 1.5rem; padding-top: 1.5rem;
} }
@@ -0,0 +1 @@
.section_footer_before
@@ -0,0 +1,34 @@
(() => {
const parseMethodsBlock = (wrapperSelector, boxSelector) => {
const container = document.querySelector(wrapperSelector);
if (!container) return null;
const titleEl = container.querySelector(`${boxSelector} h2 span`);
const title = titleEl ? titleEl.textContent.trim() : null;
const descEl = container.querySelector(`${boxSelector} .box-in p`);
const description = descEl ? descEl.textContent.trim() : null;
const imageElements = container.querySelectorAll(`${boxSelector} .box-in .ico span img`);
const methods = Array.from(imageElements).map((img) => {
return {
name: img.getAttribute('alt') || null,
imageSrc: img.getAttribute('data-src') || img.getAttribute('src') || null
};
});
return {
title,
description,
methods
};
};
const paymentMethods = parseMethodsBlock('.section_footer_before .vc-content_paymentmethods', '.box-payment');
const deliveryMethods = parseMethodsBlock('.section_footer_before .vc-content_deliverymethods', '.box-delivery');
return {
paymentMethods,
deliveryMethods
};
})();
+109
View File
@@ -0,0 +1,109 @@
:root {
--card: oklch(1 0 0);
--secondary: oklch(0.97 0 0);
--foreground: oklch(0.145 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--border: oklch(0.922 0 0);
--font-sans: "Geist", "Geist Fallback", ui-sans-serif, system-ui, sans-serif;
}
.methods-section {
font-family: var(--font-sans);
padding: 3rem 0; /* py-12 */
background-color: var(--card);
border-top: 1px solid var(--border);
}
@media (min-width: 1024px) {
.methods-section {
padding: 4rem 0; /* lg:py-16 */
}
}
.container {
max-width: 80rem; /* max-w-7xl */
margin: 0 auto;
padding: 0 1rem; /* px-4 */
}
@media (min-width: 640px) {
.container {
padding: 0 1.5rem; /* sm:px-6 */
}
}
@media (min-width: 1024px) {
.container {
padding: 0 2rem; /* lg:px-8 */
}
}
.methods-grid {
display: grid;
grid-template-columns: 1fr;
gap: 3rem; /* gap-12 */
}
@media (min-width: 768px) {
.methods-grid {
grid-template-columns: repeat(2, 1fr); /* md:grid-cols-2 */
}
}
/* Header & Description */
.section-header {
display: flex;
align-items: center;
gap: 0.5rem; /* gap-2 */
font-size: 1.125rem; /* text-lg */
font-weight: 600; /* font-semibold */
color: var(--foreground);
margin: 0 0 0.5rem 0;
}
.section-header svg {
width: 1.25rem; /* w-5 */
height: 1.25rem; /* h-5 */
color: var(--accent);
}
.section-desc {
font-size: 0.875rem;
color: var(--muted-foreground);
margin: 0 0 1.5rem 0;
line-height: 1.5;
}
/* Items Wrapper */
.items-list {
display: flex;
flex-wrap: wrap;
gap: 1rem; /* gap-4 */
}
.item-card {
display: flex;
align-items: center;
gap: 0.75rem; /* gap-3 */
padding: 0.75rem 1rem; /* px-4 py-3 */
background-color: var(--secondary);
border-radius: 0.5rem; /* rounded-lg */
border: 1px solid transparent;
transition:
background-color 0.2s,
border-color 0.2s;
}
.item-card:hover {
background-color: color-mix(in srgb, var(--secondary) 80%, transparent);
border-color: var(--border);
}
.item-img {
height: 1.5rem; /* h-6 roughly */
width: auto;
object-fit: contain;
}
.item-name {
font-size: 0.875rem; /* text-sm */
font-weight: 500; /* font-medium */
color: var(--foreground);
margin: 0;
}
@@ -0,0 +1,97 @@
<section class="methods-section">
<div class="container">
<div class="methods-grid">
<!-- Payment Methods -->
{{#if paymentMethods}}
<div>
<h3 class="section-header">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect width="20" height="14" x="2" y="5" rx="2"></rect>
<line x1="2" x2="22" y1="10" y2="10"></line>
</svg>
{{#if paymentMethods.title}}{{paymentMethods.title}}{{else}}Platební
metody{{/if}}
</h3>
{{#if paymentMethods.description}}
<p class="section-desc">{{paymentMethods.description}}</p>
{{/if}}
<div class="items-list">
{{#each paymentMethods.methods}}
<div class="item-card">
{{#if this.imageSrc}}
<img
src="{{this.imageSrc}}"
alt="{{this.name}}"
class="item-img"
loading="lazy"
/>
{{/if}}
<span class="item-name">{{this.name}}</span>
</div>
{{/each}}
</div>
</div>
{{/if}}
<!-- Delivery Methods -->
{{#if deliveryMethods}}
<div>
<h3 class="section-header">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2"
></path>
<path d="M15 18H9"></path>
<path
d="M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14"
></path>
<circle cx="17" cy="18" r="2"></circle>
<circle cx="7" cy="18" r="2"></circle>
</svg>
{{#if
deliveryMethods.title}}{{deliveryMethods.title}}{{else}}Doručovací
metody{{/if}}
</h3>
{{#if deliveryMethods.description}}
<p class="section-desc">{{deliveryMethods.description}}</p>
{{/if}}
<div class="items-list">
{{#each deliveryMethods.methods}}
<div class="item-card">
{{#if this.imageSrc}}
<img
src="{{this.imageSrc}}"
alt="{{this.name}}"
class="item-img"
loading="lazy"
/>
{{/if}}
<span class="item-name">{{this.name}}</span>
</div>
{{/each}}
</div>
</div>
{{/if}}
</div>
</div>
</section>
@@ -0,0 +1,2 @@
template_replacer:
replace_type: "hide"
@@ -0,0 +1,22 @@
document.querySelectorAll(".new-products-section .product-buy-form").forEach(form => {
const originalFormId = form.dataset.originalId;
const submitButton = form.querySelector("button");
submitButton.addEventListener("click", (e) => {
e.preventDefault();
const form = document.getElementById(originalFormId).querySelector("form");
if (form) {
form.dispatchEvent(new Event('submit', {
bubbles: true,
cancelable: true
}));
// TODO: This is awful i know but idk a better way to do this for now
setTimeout(() => {
window.Rerender.rerenderComponent("HEADER_RERENDER");
}, 2000)
}
});
});
@@ -1 +1 @@
main#main #AjaxMainSection section.section_middle .vc-commodity_attribute main#main #AjaxMainSection section.section_middle > .container > .inner > .vc-commodity_attribute
@@ -9,11 +9,11 @@
// Select all products, ignoring carousel initialization wrappers to ensure we get everything // Select all products, ignoring carousel initialization wrappers to ensure we get everything
const items = container.querySelectorAll("article.commodityBox"); const items = container.querySelectorAll("article.commodityBox");
const itemsData = Array.from(items).map((item) => { const itemsData = Array.from(items).map((item) => {
const productNode = item.querySelector("a.inner"); const productNode = item.querySelector("a.inner");
const link = productNode ? productNode.getAttribute("href") : null; const link = productNode ? productNode.getAttribute("href") : null;
// Extract flags and their computed styles // Extract flags and their computed styles
const flagsElements = productNode.querySelectorAll(".flags > .flags-inner > .flag"); const flagsElements = productNode.querySelectorAll(".flags > .flags-inner > .flag");
const flags = Array.from(flagsElements).map((flag) => { const flags = Array.from(flagsElements).map((flag) => {
@@ -40,6 +40,8 @@
// Extract buy form HTML // Extract buy form HTML
const buyFormEl = productNode.querySelector(".buyForm"); const buyFormEl = productNode.querySelector(".buyForm");
const buyFormContent = buyFormEl ? buyFormEl.innerHTML.trim() : null; const buyFormContent = buyFormEl ? buyFormEl.innerHTML.trim() : null;
const id = "button-action-id-" + crypto.randomUUID();
buyFormEl.setAttribute("id", id);
// Extract pricing details // Extract pricing details
const priceEl = productNode.querySelector(".pricing > .pricing-inner"); const priceEl = productNode.querySelector(".pricing > .pricing-inner");
@@ -50,7 +52,7 @@
if (priceEl) { if (priceEl) {
const priceDiscountEl = priceEl.querySelector(".discount .number"); const priceDiscountEl = priceEl.querySelector(".discount .number");
priceDiscount = priceDiscountEl ? parseInt(priceDiscountEl.textContent.trim(), 10) : null; priceDiscount = priceDiscountEl ? parseInt(priceDiscountEl.textContent.trim(), 10) : null;
const priceBeforeDiscountEl = priceEl.querySelector(".price.beforeDiscount"); const priceBeforeDiscountEl = priceEl.querySelector(".price.beforeDiscount");
priceBeforeDiscount = priceBeforeDiscountEl ? priceBeforeDiscountEl.textContent.trim().replace(/\s+/g, ' ') : null; priceBeforeDiscount = priceBeforeDiscountEl ? priceBeforeDiscountEl.textContent.trim().replace(/\s+/g, ' ') : null;
@@ -73,7 +75,8 @@
priceBeforeDiscount, priceBeforeDiscount,
priceDiscount, priceDiscount,
availability, availability,
buyFormContent buyFormContent,
buyFormId: id
}; };
}); });
@@ -182,9 +182,6 @@
line-height: 1.3; line-height: 1.3;
transition: color 0.2s; transition: color 0.2s;
} }
.product-card:hover .product-title {
color: var(--accent); /* group-hover:text-accent */
}
.product-pricing { .product-pricing {
display: flex; display: flex;
@@ -61,8 +61,7 @@
{{#if (eq this.availability "Skladem")}}✓ {{/if}}{{this.availability}} {{#if (eq this.availability "Skladem")}}✓ {{/if}}{{this.availability}}
</span> </span>
<div class="product-buy-form"> <div class="product-buy-form" data-original-id="{{this.buyFormId}}">
<!-- Injects the parsed <form> or <button> code directly -->
{{{this.buyFormContent}}} {{{this.buyFormContent}}}
</div> </div>
</div> </div>
@@ -0,0 +1,2 @@
template_replacer:
replace_type: "hide"
@@ -1,18 +1,14 @@
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const tabBtns = document.querySelectorAll('.js-tab-btn'); const tabBtns = document.querySelectorAll('.js-tab-btn');
const tabPanes = document.querySelectorAll('.js-tab-pane'); const tabPanes = document.querySelectorAll('.js-tab-pane');
tabBtns.forEach(btn => { tabBtns.forEach(btn => {
btn.addEventListener('click', () => { btn.addEventListener('click', () => {
// Remove active class from all buttons and panes
tabBtns.forEach(b => b.classList.remove('active')); tabBtns.forEach(b => b.classList.remove('active'));
tabPanes.forEach(p => p.classList.remove('active')); tabPanes.forEach(p => p.classList.remove('active'));
// Add active class to clicked button
btn.classList.add('active'); btn.classList.add('active');
// Show corresponding pane
const targetId = btn.getAttribute('data-target'); const targetId = btn.getAttribute('data-target');
const targetPane = document.getElementById(targetId); const targetPane = document.getElementById(targetId);
if (targetPane) { if (targetPane) {
@@ -20,4 +16,27 @@ document.addEventListener('DOMContentLoaded', () => {
} }
}); });
}); });
document.querySelectorAll(".featured-section .product-buy-form").forEach(form => {
const originalFormId = form.dataset.originalId;
const submitButton = form.querySelector("button");
submitButton.addEventListener("click", (e) => {
e.preventDefault();
const form = document.getElementById(originalFormId).querySelector("form");
if (form) {
form.dispatchEvent(new Event('submit', {
bubbles: true,
cancelable: true
}));
// TODO: This is awful i know but idk a better way to do this for now
setTimeout(() => {
window.Rerender.rerenderComponent("HEADER_RERENDER");
}, 2000)
}
});
});
}); });
@@ -15,14 +15,14 @@
for (let i = 0; i < tabTexts.length; i++) { for (let i = 0; i < tabTexts.length; i++) {
const idName = `Index_${i + 1}`; const idName = `Index_${i + 1}`;
const items = body.querySelectorAll(`#${idName} article.commodityBox`); const items = body.querySelectorAll(`#${idName} article.commodityBox`);
const itemsData = Array.from(items).map((item) => { const itemsData = Array.from(items).map((item) => {
const productNode = item.querySelector("a.inner"); const productNode = item.querySelector("a.inner");
const link = productNode.getAttribute("href"); const link = productNode.getAttribute("href");
const flagsElements = productNode.querySelectorAll(".flags > .flags-inner > .flag"); const flagsElements = productNode.querySelectorAll(".flags > .flags-inner > .flag");
const flags = Array.from(flagsElements).map((flag) => { const flags = Array.from(flagsElements).map((flag) => {
const styles = window.getComputedStyle(flag); const styles = window.getComputedStyle(flag);
@@ -45,9 +45,11 @@
const buyFormEl = productNode.querySelector(".buyForm"); const buyFormEl = productNode.querySelector(".buyForm");
const buyFormContent = buyFormEl ? buyFormEl.innerHTML.trim() : null; const buyFormContent = buyFormEl ? buyFormEl.innerHTML.trim() : null;
const id = "button-action-id-" + crypto.randomUUID();
buyFormEl.setAttribute("id", id);
const priceEl = productNode.querySelector(".pricing > .pricing-inner"); const priceEl = productNode.querySelector(".pricing > .pricing-inner");
let priceDiscount = null; let priceDiscount = null;
let priceBeforeDiscount = null; let priceBeforeDiscount = null;
let priceHighlight = null; let priceHighlight = null;
@@ -55,7 +57,7 @@
if (priceEl) { if (priceEl) {
const priceDiscountEl = priceEl.querySelector(".discount .number"); const priceDiscountEl = priceEl.querySelector(".discount .number");
priceDiscount = priceDiscountEl ? parseInt(priceDiscountEl.textContent.trim(), 10) : null; priceDiscount = priceDiscountEl ? parseInt(priceDiscountEl.textContent.trim(), 10) : null;
const priceBeforeDiscountEl = priceEl.querySelector(".price.beforeDiscount"); const priceBeforeDiscountEl = priceEl.querySelector(".price.beforeDiscount");
priceBeforeDiscount = priceBeforeDiscountEl ? priceBeforeDiscountEl.textContent.trim().replace(/\s+/g, ' ') : null; priceBeforeDiscount = priceBeforeDiscountEl ? priceBeforeDiscountEl.textContent.trim().replace(/\s+/g, ' ') : null;
@@ -77,7 +79,8 @@
priceBeforeDiscount, priceBeforeDiscount,
priceDiscount, priceDiscount,
availability, availability,
buyFormContent buyFormContent,
buyFormId: id
}; };
}); });
@@ -203,9 +203,6 @@
line-height: 1.3; line-height: 1.3;
transition: color 0.2s; transition: color 0.2s;
} }
.product-card:hover .product-title {
color: var(--accent);
}
.product-pricing { .product-pricing {
display: flex; display: flex;
@@ -77,7 +77,7 @@
{{/if}}{{this.availability}} {{/if}}{{this.availability}}
</span> </span>
<div class="product-buy-form">{{{this.buyFormContent}}}</div> <div class="product-buy-form" data-original-id="{{this.buyFormId}}">{{{this.buyFormContent}}}</div>
</div> </div>
</div> </div>
</div> </div>