almost all ready

This commit is contained in:
2026-05-14 01:48:15 +02:00
parent 1b53c1c199
commit d55b0e2914
29 changed files with 5489 additions and 650 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+45 -6
View File
@@ -32,7 +32,7 @@
@layer components {
.site-container {
@apply mx-auto w-full max-w-7xl px-4 sm:px-5 lg:px-8;
@apply mx-auto w-full max-w-[104rem] px-4 sm:px-5 lg:px-8;
}
.site-header {
@@ -96,7 +96,7 @@
}
.header-bar {
@apply flex flex-wrap items-center gap-4 py-4 sm:gap-5 sm:py-5 lg:flex-nowrap;
@apply flex flex-wrap items-center gap-3 py-3 sm:gap-5 sm:py-5 lg:flex-nowrap;
}
.brand-mark {
@@ -108,11 +108,11 @@
}
.menu-toggle {
@apply inline-flex h-11 items-center justify-center rounded-full border border-stone-300/70 bg-white/80 px-5 text-xs font-semibold uppercase tracking-[0.28em] text-stone-900 transition hover:border-amber-500 hover:text-amber-600;
@apply inline-flex h-11 items-center justify-center rounded-full border border-stone-300/70 bg-white/90 px-5 text-[0.68rem] font-semibold uppercase tracking-[0.28em] text-stone-900 shadow-[0_10px_24px_rgba(20,33,61,0.08)] transition hover:border-amber-500 hover:text-amber-600;
}
.menu-panel {
@apply order-last w-full rounded-3xl border border-white/70 bg-white/95 p-4 shadow-[0_16px_34px_rgba(20,33,61,0.12)] backdrop-blur lg:order-none lg:rounded-none lg:border-0 lg:bg-transparent lg:p-0 lg:shadow-none;
@apply order-last mt-2 w-full rounded-[1.75rem] border border-white/70 bg-white/95 p-4 shadow-[0_16px_34px_rgba(20,33,61,0.12)] backdrop-blur lg:order-none lg:mt-0 lg:rounded-none lg:border-0 lg:bg-transparent lg:p-0 lg:shadow-none;
}
.desktop-nav {
@@ -158,11 +158,11 @@
}
.header-actions {
@apply ml-0 flex items-center gap-4 text-2xl text-stone-900 lg:ml-auto;
@apply order-2 ml-auto flex items-center gap-2 text-2xl text-stone-900 lg:ml-auto lg:gap-4;
}
.nav-icon {
@apply flex h-11 w-11 items-center justify-center rounded-full bg-white/70 text-[1.35rem] shadow-[0_10px_24px_rgba(20,33,61,0.08)] transition hover:-translate-y-0.5 hover:text-amber-600;
@apply flex h-11 w-11 items-center justify-center rounded-full bg-white/80 text-[1.2rem] shadow-[0_10px_24px_rgba(20,33,61,0.08)] transition hover:-translate-y-0.5 hover:text-amber-600 sm:text-[1.35rem];
}
@media (max-width: 1023px) {
@@ -170,6 +170,45 @@
.mega-menu {
display: none;
}
.utility-bar .site-container {
@apply gap-2 py-2.5;
}
.utility-bar a[href^="mailto:"] {
@apply hidden;
}
.header-locale {
@apply grid w-full grid-cols-2 gap-2;
}
.locale-picker,
.locale-picker__summary {
@apply w-full;
}
.brand-mark {
@apply text-[2.2rem];
}
.header-bar {
@apply gap-y-3;
}
.menu-panel {
@apply max-h-[70vh] overflow-y-auto p-3;
}
}
@media (max-width: 639px) {
.header-actions .nav-icon:first-child {
display: none;
}
.menu-toggle {
@apply px-4;
}
}
button[type='submit'] {
+556
View File
@@ -106,3 +106,559 @@ if (cartButton) {
root.style.setProperty("--cta-glow", "0 0 0 0 rgba(0,0,0,0)");
});
}
const productMainImage = document.querySelector("[data-product-main-image]");
const defaultProductImage = productMainImage?.dataset.defaultImage || productMainImage?.getAttribute("src") || "";
const productThumbCarousel = document.querySelector("[data-product-thumb-carousel]");
const productThumbViewport = productThumbCarousel?.querySelector("[data-product-thumb-viewport]");
const productThumbTrack = productThumbCarousel?.querySelector("[data-product-thumb-track]");
const productThumbPrev = productThumbCarousel?.querySelector("[data-product-thumb-prev]");
const productThumbNext = productThumbCarousel?.querySelector("[data-product-thumb-next]");
const productThumbs = [...document.querySelectorAll("[data-product-thumb-index]")];
if (productMainImage && productThumbCarousel && productThumbViewport && productThumbTrack && productThumbs.length > 0) {
let activeProductThumbIndex = 0;
const updateProductThumbState = () => {
productThumbs.forEach((thumb, index) => {
if (index === activeProductThumbIndex) {
thumb.classList.remove("border-stone-800");
thumb.classList.add("border-amber-400/60", "ring-1", "ring-amber-300/40");
thumb.setAttribute("aria-current", "true");
return;
}
thumb.classList.remove("border-amber-400/60", "ring-1", "ring-amber-300/40");
thumb.classList.add("border-stone-800");
thumb.removeAttribute("aria-current");
});
};
const scrollProductThumbIntoView = (index) => {
const thumb = productThumbs[index];
thumb?.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center" });
};
const showProductThumb = (index) => {
const normalizedIndex = (index + productThumbs.length) % productThumbs.length;
const thumb = productThumbs[normalizedIndex];
const nextSrc = thumb?.dataset.productThumbLarge || thumb?.dataset.productThumbLargeFallback || "";
if (!thumb || !nextSrc) {
return;
}
activeProductThumbIndex = normalizedIndex;
productMainImage.setAttribute("src", nextSrc);
if (thumb.dataset.productThumbAlt) {
productMainImage.setAttribute("alt", thumb.dataset.productThumbAlt);
}
updateProductThumbState();
scrollProductThumbIntoView(normalizedIndex);
};
const stepProductThumbs = (direction) => {
showProductThumb(activeProductThumbIndex + direction);
};
productThumbPrev?.addEventListener("click", () => {
stepProductThumbs(-1);
});
productThumbNext?.addEventListener("click", () => {
stepProductThumbs(1);
});
productThumbs.forEach((thumb, index) => {
thumb.addEventListener("click", () => {
showProductThumb(index);
});
});
const initialIndex = productThumbs.findIndex((thumb) => thumb.dataset.productThumbLarge === productMainImage.getAttribute("src"));
showProductThumb(initialIndex >= 0 ? initialIndex : 0);
productThumbViewport.addEventListener(
"wheel",
(event) => {
if (Math.abs(event.deltaY) <= Math.abs(event.deltaX)) {
return;
}
event.preventDefault();
productThumbViewport.scrollBy({ left: event.deltaY, behavior: "smooth" });
},
{ passive: false },
);
}
const variantPicker = document.querySelector("[data-variant-picker]");
const variantCombinationInput = document.querySelector("[data-variant-combination]");
if (variantPicker && variantCombinationInput) {
const interactiveGroups = [...variantPicker.querySelectorAll("[data-variant-group]")];
const variantSummary = document.querySelector("[data-variant-selection-summary]");
const productPriceGross = document.querySelector("[data-product-price-gross]");
const productPriceNet = document.querySelector("[data-product-price-net]");
const defaultPriceGross = productPriceGross?.dataset.defaultPriceGross || productPriceGross?.textContent || "";
const defaultPriceNet = productPriceNet?.dataset.defaultPriceNet || productPriceNet?.textContent || "";
const combinationImageNodes = [...document.querySelectorAll("[data-variant-combination-image]")];
const combinationImageByID = new Map(
combinationImageNodes.map((node) => [
node.dataset.variantCombinationImage,
{
imageLarge: node.dataset.imageLarge || "",
priceGross: node.dataset.priceGross || "",
priceNet: node.dataset.priceNet || "",
},
]),
);
const parseCombinationIDs = (value) =>
(value || "")
.split(",")
.map((item) => Number.parseInt(item, 10))
.filter((item) => Number.isInteger(item) && item > 0);
const optionClassName = (presentation, selected, disabled) => {
if (presentation === "radio") {
if (disabled) {
return "rounded-full border border-stone-200 bg-stone-100 px-4 py-2 text-sm font-medium text-stone-400 opacity-50 transition";
}
if (selected) {
return "rounded-full border border-stone-900 bg-stone-900 px-4 py-2 text-sm font-medium text-stone-50 transition";
}
return "rounded-full border border-stone-300 bg-white px-4 py-2 text-sm font-medium text-stone-900 transition hover:border-stone-700";
}
if (presentation === "select") {
if (disabled) {
return "w-full rounded-2xl px-4 py-3 text-left text-sm font-medium text-stone-400 opacity-50 transition";
}
if (selected) {
return "w-full rounded-2xl bg-stone-900 px-4 py-3 text-left text-sm font-medium text-stone-50 transition";
}
return "w-full rounded-2xl px-4 py-3 text-left text-sm font-medium text-stone-700 transition hover:bg-stone-100 hover:text-stone-950";
}
if (disabled) {
return "inline-flex min-h-10 min-w-10 items-center justify-center border border-stone-200 p-0.5 opacity-40 transition";
}
if (selected) {
return "inline-flex min-h-10 min-w-10 items-center justify-center border border-stone-900 p-0.5 ring-1 ring-stone-900 transition";
}
return "inline-flex min-h-10 min-w-10 items-center justify-center border border-stone-300 p-0.5 transition hover:border-stone-700";
};
const intersectIDs = (left, right) => left.filter((id) => right.includes(id));
const availableCombinationIDsForGroup = (targetGroup) => {
let matched = null;
interactiveGroups.forEach((group) => {
if (group === targetGroup) {
return;
}
const activeButton = group.querySelector("[data-variant-option][data-selected='true']");
const ids = parseCombinationIDs(activeButton?.dataset.combinationIds);
if (ids.length === 0) {
return;
}
if (matched === null) {
matched = ids;
return;
}
matched = intersectIDs(matched, ids);
});
return matched;
};
const selectedValueForGroup = (group) => {
if (group.dataset.variantSelect !== undefined) {
const triggerValue = group.querySelector("[data-variant-select-value]");
return triggerValue?.textContent?.trim() || "";
}
const activeButton = group.querySelector("[data-variant-option][data-selected='true']");
return activeButton?.getAttribute("aria-label")?.trim() || activeButton?.textContent?.trim() || "";
};
const updateSelectionSummary = () => {
const parts = [];
interactiveGroups.forEach((group) => {
const key = group.dataset.variantGroup;
const value = selectedValueForGroup(group);
const labelNode = key
? document.querySelector(`[data-variant-current="${key}"]`)
: null;
if (labelNode) {
labelNode.textContent = value;
}
const wrapper = group.closest(".space-y-3");
const label = wrapper?.querySelector("p")?.textContent?.trim() || "";
if (label && value) {
parts.push(`${label}: ${value}`);
}
});
if (variantSummary) {
variantSummary.textContent = parts.length > 0 ? parts.join(" • ") : "Choose product options";
}
};
const updateButtonSelection = (group, activeButton) => {
group.querySelectorAll("[data-variant-option]").forEach((button) => {
const isActive = button === activeButton;
button.dataset.selected = isActive ? "true" : "false";
const disabled = button.dataset.disabled === "true";
button.className = optionClassName(button.dataset.variantPresentation, isActive, disabled);
});
};
const syncSelectTriggerValue = (group) => {
if (group.dataset.variantSelect === undefined) {
return;
}
const valueNode = group.querySelector("[data-variant-select-value]");
const activeButton = group.querySelector("[data-variant-option][data-selected='true']");
if (valueNode && activeButton) {
valueNode.textContent = activeButton.textContent?.trim() || "";
}
};
const refreshGroupAvailability = (group) => {
const availableIDs = availableCombinationIDsForGroup(group);
const options = [...group.querySelectorAll("[data-variant-option]")];
options.forEach((button) => {
const optionIDs = parseCombinationIDs(button.dataset.combinationIds);
const enabled = availableIDs === null || intersectIDs(optionIDs, availableIDs).length > 0;
button.dataset.disabled = enabled ? "false" : "true";
button.disabled = !enabled;
button.setAttribute("aria-disabled", enabled ? "false" : "true");
});
const selectedButton = group.querySelector("[data-variant-option][data-selected='true']");
if (selectedButton?.dataset.disabled === "true") {
selectedButton.dataset.selected = "false";
}
let nextSelected = group.querySelector("[data-variant-option][data-selected='true'][data-disabled='false']");
if (!nextSelected) {
nextSelected = group.querySelector("[data-variant-option][data-disabled='false']");
}
if (nextSelected) {
updateButtonSelection(group, nextSelected);
syncSelectTriggerValue(group);
} else {
options.forEach((button) => {
button.dataset.selected = "false";
button.className = optionClassName(button.dataset.variantPresentation, false, true);
});
}
};
const normalizeVariantSelections = () => {
for (let pass = 0; pass < 2; pass += 1) {
interactiveGroups.forEach((group) => {
refreshGroupAvailability(group);
});
}
};
const resolveCombination = () => {
normalizeVariantSelections();
let matched = null;
interactiveGroups.forEach((group) => {
const activeButton = group.querySelector("[data-variant-option][data-selected='true']");
const ids = parseCombinationIDs(activeButton?.dataset.combinationIds);
if (ids.length === 0) {
matched = [];
return;
}
if (matched === null) {
matched = ids;
return;
}
matched = matched.filter((id) => ids.includes(id));
});
if (matched && matched.length > 0) {
const combinationID = String(matched[0]);
variantCombinationInput.value = combinationID;
const combinationData = combinationImageByID.get(combinationID);
if (productMainImage) {
const nextImage = combinationData?.imageLarge || defaultProductImage;
if (nextImage) {
productMainImage.setAttribute("src", nextImage);
const matchingThumbIndex = productThumbs.findIndex((thumb) => thumb.dataset.productThumbLarge === nextImage);
if (matchingThumbIndex >= 0) {
productThumbs.forEach((thumb, index) => {
if (index === matchingThumbIndex) {
thumb.classList.remove("border-stone-800");
thumb.classList.add("border-amber-400/60", "ring-1", "ring-amber-300/40");
thumb.setAttribute("aria-current", "true");
} else {
thumb.classList.remove("border-amber-400/60", "ring-1", "ring-amber-300/40");
thumb.classList.add("border-stone-800");
thumb.removeAttribute("aria-current");
}
});
productThumbs[matchingThumbIndex]?.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center" });
}
}
}
if (productPriceGross) {
productPriceGross.textContent = combinationData?.priceGross || defaultPriceGross;
}
if (productPriceNet) {
productPriceNet.textContent = combinationData?.priceNet || defaultPriceNet;
}
} else {
variantCombinationInput.value = "";
if (productPriceGross) {
productPriceGross.textContent = defaultPriceGross;
}
if (productPriceNet) {
productPriceNet.textContent = defaultPriceNet;
}
}
updateSelectionSummary();
};
const closeVariantSelects = () => {
interactiveGroups.forEach((group) => {
if (group.dataset.variantSelect === undefined) return;
group.querySelector("[data-variant-select-menu]")?.classList.add("hidden");
group.querySelector("[data-variant-select-trigger]")?.setAttribute("aria-expanded", "false");
});
};
interactiveGroups.forEach((group) => {
if (group.dataset.variantSelect !== undefined) {
const trigger = group.querySelector("[data-variant-select-trigger]");
const menu = group.querySelector("[data-variant-select-menu]");
const valueNode = group.querySelector("[data-variant-select-value]");
trigger?.addEventListener("click", () => {
const isOpen = trigger.getAttribute("aria-expanded") === "true";
closeVariantSelects();
if (isOpen) {
return;
}
menu?.classList.remove("hidden");
trigger.setAttribute("aria-expanded", "true");
});
group.querySelectorAll("[data-variant-option]").forEach((button) => {
button.addEventListener("click", () => {
if (button.dataset.disabled === "true") {
return;
}
updateButtonSelection(group, button);
if (valueNode) {
valueNode.textContent = button.textContent?.trim() || "";
}
closeVariantSelects();
resolveCombination();
});
});
return;
}
group.querySelectorAll("[data-variant-option]").forEach((button) => {
button.addEventListener("click", () => {
if (button.dataset.disabled === "true") {
return;
}
updateButtonSelection(group, button);
resolveCombination();
});
});
});
resolveCombination();
document.addEventListener("click", (event) => {
const target = event.target;
if (!(target instanceof Node)) return;
const insidePicker = variantPicker.contains(target);
if (!insidePicker) {
closeVariantSelects();
}
});
document.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
closeVariantSelects();
}
});
}
const galleryModal = document.querySelector("[data-gallery-modal]");
const galleryMain = galleryModal?.querySelector("[data-gallery-main]");
const galleryOpeners = [...document.querySelectorAll("[data-gallery-open]")];
const galleryClosers = [...document.querySelectorAll("[data-gallery-close]")];
const galleryThumbs = [...document.querySelectorAll("[data-gallery-thumb]")];
const galleryPrev = galleryModal?.querySelector("[data-gallery-prev]");
const galleryNext = galleryModal?.querySelector("[data-gallery-next]");
const galleryThumbViewport = galleryModal?.querySelector("[data-gallery-thumb-viewport]");
const galleryThumbPrev = galleryModal?.querySelector("[data-gallery-thumb-prev]");
const galleryThumbNext = galleryModal?.querySelector("[data-gallery-thumb-next]");
if (galleryModal && galleryMain && galleryOpeners.length > 0) {
let activeIndex = 0;
let wheelLocked = false;
const openGallery = () => {
const productMainImage = document.querySelector("[data-product-main-image]");
const currentMainSrc = productMainImage?.getAttribute("src") || "";
if (currentMainSrc) {
const matchingIndex = galleryThumbs.findIndex((thumb) => thumb.dataset.galleryThumb === currentMainSrc);
if (matchingIndex >= 0) {
showGalleryImage(matchingIndex);
}
}
galleryModal.classList.remove("hidden");
galleryModal.setAttribute("aria-hidden", "false");
document.body.style.overflow = "hidden";
};
const closeGallery = () => {
galleryModal.classList.add("hidden");
galleryModal.setAttribute("aria-hidden", "true");
document.body.style.overflow = "";
};
const setActiveThumb = (activeThumb) => {
galleryThumbs.forEach((thumb) => {
thumb.classList.remove("border-amber-400/60");
thumb.classList.add("border-stone-800");
});
if (activeThumb) {
activeThumb.classList.remove("border-stone-800");
activeThumb.classList.add("border-amber-400/60");
activeThumb.scrollIntoView({ block: "nearest", inline: "nearest" });
}
};
const showGalleryImage = (index) => {
if (galleryThumbs.length === 0) return;
const normalizedIndex = (index + galleryThumbs.length) % galleryThumbs.length;
const thumb = galleryThumbs[normalizedIndex];
const nextSrc = thumb.dataset.galleryThumb;
const nextAlt = thumb.dataset.galleryAlt || galleryMain.getAttribute("alt") || "";
if (!nextSrc) return;
activeIndex = normalizedIndex;
galleryMain.setAttribute("src", nextSrc);
galleryMain.setAttribute("alt", nextAlt);
setActiveThumb(thumb);
};
const stepGallery = (direction) => {
if (galleryThumbs.length <= 1) return;
showGalleryImage(activeIndex + direction);
};
galleryThumbPrev?.addEventListener("click", () => {
stepGallery(-1);
});
galleryThumbNext?.addEventListener("click", () => {
stepGallery(1);
});
galleryOpeners.forEach((trigger) => {
trigger.addEventListener("click", openGallery);
});
galleryClosers.forEach((trigger) => {
trigger.addEventListener("click", closeGallery);
});
galleryThumbs.forEach((thumb, index) => {
if (index === 0) {
showGalleryImage(0);
}
thumb.addEventListener("click", () => {
showGalleryImage(index);
});
});
galleryPrev?.addEventListener("click", () => {
stepGallery(-1);
});
galleryNext?.addEventListener("click", () => {
stepGallery(1);
});
galleryThumbViewport?.addEventListener(
"wheel",
(event) => {
if (window.innerWidth >= 1024) {
event.preventDefault();
galleryThumbViewport.scrollBy({ top: event.deltaY, behavior: "smooth" });
return;
}
if (Math.abs(event.deltaY) <= Math.abs(event.deltaX)) {
return;
}
event.preventDefault();
galleryThumbViewport.scrollBy({ left: event.deltaY, behavior: "smooth" });
},
{ passive: false },
);
galleryModal.addEventListener("click", (event) => {
if (event.target === galleryModal) {
closeGallery();
}
});
galleryModal.addEventListener(
"wheel",
(event) => {
if (galleryModal.getAttribute("aria-hidden") !== "false" || galleryThumbs.length <= 1) {
return;
}
if (Math.abs(event.deltaY) < 10) {
return;
}
event.preventDefault();
if (wheelLocked) {
return;
}
wheelLocked = true;
stepGallery(event.deltaY > 0 ? 1 : -1);
window.setTimeout(() => {
wheelLocked = false;
}, 180);
},
{ passive: false },
);
document.addEventListener("keydown", (event) => {
if (galleryModal.getAttribute("aria-hidden") !== "false") {
return;
}
if (event.key === "Escape") {
closeGallery();
return;
}
if (event.key === "ArrowLeft") {
event.preventDefault();
stepGallery(-1);
return;
}
if (event.key === "ArrowRight") {
event.preventDefault();
stepGallery(1);
}
});
}