// Product detail page (PDP). (function () { if (typeof React === "undefined") return; const DS = window.VElaStvNajmanDesignSystem_bc3fc2; const { Photo, Button, Badge, Tag, Rating, QuantityStepper, SectionHeading, Input } = DS; const Icon = window.VNIcon; function Accordion({ title, children, open: defOpen }) { const [open, setOpen] = React.useState(!!defOpen); return (

{children}

); } // Count-up animation for the price (3.3) — eases the number to its new value. function useCountUp(value, ms) { const [disp, setDisp] = React.useState(value); const prev = React.useRef(value); React.useEffect(() => { const from = prev.current, to = value; prev.current = value; if (from === to) return; const reduce = window.matchMedia && !window.matchMedia('(prefers-reduced-motion: no-preference)').matches; if (reduce) { setDisp(to); return; } let raf, start; const step = (t) => { if (!start) start = t; const p = Math.min(1, (t - start) / ms); setDisp(Math.round(from + (to - from) * p)); if (p < 1) raf = requestAnimationFrame(step); }; raf = requestAnimationFrame(step); return () => cancelAnimationFrame(raf); }, [value, ms]); return disp; } function Product({ go, addToCart, onNotify, onPreorder, productId }) { const D = window.SHOP_DATA; const p = D.products.find((x) => x.id === productId) || D.products[0]; const honeyVariants = p.variants && p.variants.length ? p.variants : null; const [qty, setQty] = React.useState(1); const [activeImg, setActiveImg] = React.useState(0); const [lightbox, setLightbox] = React.useState(false); const touchX = React.useRef(null); const onTouchStart = (e) => { touchX.current = e.touches[0].clientX; }; const onTouchEnd = (e) => { if (touchX.current == null) return; const dx = e.changedTouches[0].clientX - touchX.current; touchX.current = null; if (Math.abs(dx) < 40) return; setActiveImg((i) => Math.max(0, Math.min(3, dx < 0 ? i + 1 : i - 1))); }; const [vIdx, setVIdx] = React.useState(0); React.useEffect(() => { setQty(1); setActiveImg(0); setLightbox(false); const di = honeyVariants ? Math.max(0, honeyVariants.findIndex(v => v.w === p.weight)) : 0; setVIdx(di); }, [productId]); const variant = honeyVariants ? honeyVariants[Math.min(vIdx, honeyVariants.length - 1)] : null; const curPrice = variant ? variant.price : p.price; const curWeight = variant ? variant.w : p.weight; const dispPrice = useCountUp(curPrice * qty, 150); // 4.1 — sticky buy bar appears only once the real buy box scrolls out of view. // Uses a scroll listener (works everywhere, incl. environments where IO is flaky). const buyRef = React.useRef(null); const [showSticky, setShowSticky] = React.useState(false); React.useEffect(() => { const el = buyRef.current; if (!el) return; const check = () => { const r = el.getBoundingClientRect(); setShowSticky(r.bottom < 80); }; check(); window.addEventListener("scroll", check, { passive: true }); window.addEventListener("resize", check); return () => { window.removeEventListener("scroll", check); window.removeEventListener("resize", check); }; }, [productId, vIdx, soldOut, notified]); // close lightbox on Escape React.useEffect(() => { if (!lightbox) return; const onKey = (e) => { if (e.key === "Escape") setLightbox(false); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [lightbox]); const curStock = variant ? variant.stock : (p.stock || "in"); const soldOut = curStock === "out"; const curRestock = (variant && variant.restock) || p.restock || null; const [notifyEmail, setNotifyEmail] = React.useState(""); const [notified, setNotified] = React.useState(false); React.useEffect(() => { setNotified(false); setNotifyEmail(""); }, [productId, vIdx]); const buyItem = { ...p, id: variant ? p.id + "-" + variant.w : p.id, name: p.name, price: curPrice, weight: curWeight }; const preorderItem = { ...p, price: curPrice, weight: curWeight, restock: curRestock }; const HONEY = ["kvetove", "pastovany", "lesni"]; const related = (() => { const pool = D.products.filter((x) => x.id !== p.id); const seen = new Set(); const out = []; const add = (arr) => arr.forEach((x) => { if (!seen.has(x.id) && out.length < 3) { seen.add(x.id); out.push(x); } }); add(pool.filter((x) => x.cat === p.cat)); // same category first if (HONEY.includes(p.cat)) add(pool.filter((x) => HONEY.includes(x.cat))); // then honey siblings add(pool.filter((x) => x.cat !== "gift")); // then anything else (skip gift bundle) add(pool); // final fallback return out; })(); const MENU_CAT = { carnica: { label: "Kraňské matky", arg: "carnica" }, buckfast: { label: "Buckfast matky", arg: "buckfast" }, inseminace: { label: "Inseminované", arg: "inseminace" }, oddelky: { label: "Oddělky a včelstva", arg: "oddelky" }, }; const crumbCat = MENU_CAT[p.cat] || { label: "Matky", arg: "carnica" }; return (
{/* GALLERY */}
setLightbox(true)} onTouchStart={onTouchStart} onTouchEnd={onTouchEnd} title="Klikněte pro zvětšení">
{[0,1,2,3].map((i) => (
{[0,1,2,3].map((i) => ( { setActiveImg(i); }} /> ))}
{/* INFO */}

{p.name}

{curStock === "out" ? "Vyprodáno" : curStock === "low" ? "Poslední kusy" : "Skladem"}
{p.taste &&

{p.taste}

}

{p.desc}

{/* SELECTION CARD — balení + cena pohromadě, jasně barevně odlišené */}
{honeyVariants && (
Vyberte balení
{honeyVariants.map((v, i) => ( ))}
)}
{curPrice > 0 ? dispPrice + " Kč" : "Cena na dotaz"} vč. DPH{qty > 1 ? " · " + qty + " ks" : ""} · {curWeight}
{soldOut ? ( curRestock ? (

Naskladňujeme: {curRestock}

) : (
{notified ? (

Dáme vědět, jakmile bude {curWeight ? curWeight + " " : ""}skladem.

) : (
{ e.preventDefault(); if (notifyEmail) setNotified(true); }}> setNotifyEmail(e.target.value)} />
)}
) ) : (
)}
Osobní převzetí na včelnici v Hostivicích · matky zasíláme i poštou
{/* PROVENANCE — původ a kvalita: mapka + data */}

Původ a kvalita

Původ / linie
{p.region}
Období
{p.prov ? p.prov.harvest : "—"}
Ročník
{p.prov ? p.prov.year : "—"}
Evidenční číslo
{p.prov ? p.prov.lot : "—"}
Veterinární osvědčení k dispozici
{/* TASTE PROFILE */} {window.VNTasteProfile && p.profile && (p.profile.color > 0 || p.profile.sweet > 0 || (p.profile.traits && p.profile.traits.length)) && (

Vlastnosti linie

)}
Matky předáváme osobně na včelnici v Hostivicích po domluvě, nebo je posíláme v klíckách doporučeně po ČR. Oddělky a včelstva se přebírají výhradně osobně. Matku přidávejte v klícce do včelstva bez matky, za příznivého počasí a snůšky. Plný postup najdete v našem blogu v sekci Sezónní práce. Ke každé zásilce přikládáme veterinární osvědčení o zdraví včelstev. Přesun včel mezi stanovišti se řídí platnou legislativou.
{/* RELATED */}
{related.map((r) => ( { go("product", pr.id); window.scrollTo(0,0); }} onAdd={() => addToCart(r)} onNotify={onNotify} onPreorder={onPreorder} /> ))}
{/* 4.2 fullscreen lightbox */} {lightbox && (
setLightbox(false)} role="dialog" aria-modal="true" aria-label={p.name}>
e.stopPropagation()} onTouchStart={onTouchStart} onTouchEnd={onTouchEnd}>
{[0,1,2,3].map((i) => (
)} {/* sticky mobile bar — only once the real buy box is out of view */}
{curPrice > 0 ? curPrice + " Kč" : "Cena na dotaz"}{curPrice > 0 ? vč. DPH{honeyVariants ? " · " + curWeight : ""} : null} {soldOut ? (curRestock ? : ) : }
); } window.VNProduct = Product; })();