/* global React, MachineRender */ const { useState: useSP, useEffect: useEP } = React; const isSubdir = window.location.pathname.includes("/products/"); const prefix = isSubdir ? "../../" : ""; function getProductBrochure(data) { if (data && data.brochureUrl) return data.brochureUrl; const name = ((data && data.name) || "").toLowerCase(); const p = window.location.pathname.toLowerCase(); // 1. Auto Coater Tablet -> Tablet Coating brochure if (name.includes("auto tablet coater") || name.includes("auto coater") || p.includes("auto-coater")) { return `${prefix}assets/brochure/Brochure - Mark Maker - Tablet Coating.pdf`; } // 2. EFF PACK, EFF PRO, VTD -> Effervescent brochure const isEffPack = name.includes("eff pack") || name.includes("eff-pack") || name.includes("tube filling") || p.includes("eff-pack"); const isEffPro = name.includes("eff pro") || name.includes("eff-pro") || name.includes("effervescent processing") || p.includes("eff-pro"); const isVtd = name.includes("vacuum tray dryer") || name.includes("vtd") || p.includes("vtd"); if (isEffPack || isEffPro || isVtd) { return `${prefix}assets/brochure/Brochure - Mark Maker - Effervescent.pdf`; } // 3. Rest of the product pages -> Granulation brochure return `${prefix}assets/brochure/Brochure - Mark Maker - Granulation.pdf`; } function getProductGADrawing(data) { if (data && data.gaDrawingUrl) return data.gaDrawingUrl; const name = ((data && data.name) || "").toLowerCase(); const p = window.location.pathname.toLowerCase(); // 1. RMG -> RMG 150 PDF.pdf if (name.includes("rapid mixer") || name.includes("rapid mixing") || name.includes("rmg") || p.includes("rapid-mixing-granulator")) { return `${prefix}assets/2d-drawings/2D DWG/RMG 150 PDF.pdf`; } // 2. Auto Tablet Coater -> AUTOCOATER 48.pdf if (name.includes("auto tablet coater") || name.includes("auto coater") || p.includes("auto-coater")) { return `${prefix}assets/2d-drawings/2D DWG/auto-tablet-coater/AUTOCOATER 48.pdf`; } // 3. Conta Pillar Blender (CPB) -> CPB-2000LTRS.pdf if (name.includes("conta pillar") || name.includes("cpb") || p.includes("cpb.html")) { return `${prefix}assets/2d-drawings/2D DWG/conta-pillar-blender/CPB-2000LTRS.pdf`; } // 4. Effervescent Packaging (EFF-PACK) -> EFF PACK 100.pdf if (name.includes("eff pack") || name.includes("eff-pack") || name.includes("tube filling") || p.includes("eff-pack")) { return `${prefix}assets/2d-drawings/2D DWG/effervescent-packaging/EFF PACK 100.pdf`; } // 5. Effervescent Processing (EFF-PRO) -> EFF PRO 250.pdf if (name.includes("eff pro") || name.includes("eff-pro") || name.includes("effervescent processing") || p.includes("eff-pro")) { return `${prefix}assets/2d-drawings/2D DWG/effervescent-processing/EFF PRO 250.pdf`; } // 6. Fluid Bed Coater (FBC) -> FBC-300KG.pdf if (name.includes("fluid bed coater") || name.includes("fbc") || p.includes("fbc.html")) { return `${prefix}assets/2d-drawings/2D DWG/fluid-bed-coater/FBC-300KG.pdf`; } // 7. Fluid Bed Dryer (FBD) -> MMPE-FBD-120KG.pdf if (name.includes("fluid bed dryer") || name.includes("fbd") || p.includes("fbd.html")) { return `${prefix}assets/2d-drawings/2D DWG/fluid-bed-dryer/MMPE-FBD-120KG.pdf`; } // 8. IPC Bin / IBC Bin Container -> IPC BIN.pdf if (name.includes("ipc bin") || name.includes("ibc bin") || p.includes("ipc-bin")) { return `${prefix}assets/2d-drawings/2D DWG/ibc-bin-container/IPC BIN.pdf`; } // 9. Lifting & Positioning Device (LPD) -> LPU.pdf if (name.includes("lifting and positioning") || name.includes("lifting & positioning") || name.includes("lpd") || p.includes("lpd.html")) { return `${prefix}assets/2d-drawings/2D DWG/lifting-and-positioning-device/LPU.pdf`; } // 10. Vacuum Tray Dryer (VTD) -> VTS.pdf if (name.includes("vacuum tray dryer") || name.includes("vtd") || p.includes("vtd.html")) { return `${prefix}assets/2d-drawings/2D DWG/vaccum-tray-dryer/VTS.pdf`; } return null; } function getProductLayoutKey(data) { const name = ((data && data.name) || "").toLowerCase(); const p = window.location.pathname.toLowerCase(); if (name.includes("rapid mixer") || name.includes("rapid mixing") || name.includes("rmg") || p.includes("rapid-mixing-granulator")) return "rmg"; if (name.includes("auto tablet coater") || name.includes("auto coater") || p.includes("auto-coater")) return "auto-coater"; if (name.includes("conta pillar") || name.includes("cpb") || p.includes("cpb.html")) return "cpb"; if (name.includes("eff pack") || name.includes("eff-pack") || name.includes("tube filling") || p.includes("eff-pack")) return "eff-pack"; if (name.includes("eff pro") || name.includes("eff-pro") || name.includes("effervescent processing") || p.includes("eff-pro")) return "eff-pro"; if (name.includes("fluid bed coater") || name.includes("fbc") || p.includes("fbc.html")) return "fbc"; if (name.includes("fluid bed dryer") || name.includes("fbd") || p.includes("fbd.html")) return "fbd"; if (name.includes("ipc bin") || name.includes("ibc bin") || p.includes("ipc-bin")) return "ipc-bin"; if (name.includes("lifting and positioning") || name.includes("lifting & positioning") || name.includes("lpd") || p.includes("lpd.html")) return "lpd"; if (name.includes("vacuum tray dryer") || name.includes("vtd") || p.includes("vtd.html")) return "vtd"; return null; } const PRODUCT_LAYOUT_CONFIG = { rmg: { title: "Compact footprint, easy cleanroom integration.", desc: "Our Rapid Mixer Granulator (RMG 150) is built on a space-saving 2.1 × 1.2 m base. It offers 3-side operator access for easy parameter tweaking, while maintenance doors open from the back.", specs: [ ["Mixing Bowl Diameter", "750 mm"], ["Total Height (Lid Closed)", "1950 mm"], ["Discharge Valve Clearance", "1000 mm (suited for direct FBD connection)"], ["Required Room Height", "Minimum 2500 mm clearance"], ["Net Weight", "Approx. 1200 kg"], ] }, "auto-coater": { title: "Ergonomic layout, fast cleanroom duct integration.", desc: "Our Auto Tablet Coater 48 offers front-facing operator access for spray gun calibration and pan loading, with rear service doors for exhaust and drive systems.", specs: [ ["Coating Pan Diameter", "48 inches (1220 mm)"], ["Total Height", "2150 mm"], ["Peristaltic Pump Clearance", "800 mm front access"], ["Required Room Height", "Minimum 2800 mm clearance"], ["Net Weight", "Approx. 1850 kg"], ] }, cpb: { title: "Flexible blending footprint, 360° safety envelope.", desc: "Our Conta Pillar Blender is engineered with a compact clamping column and generous 360° rotation clearance, suitable for varied IBC bin capacities.", specs: [ ["Blending Arm Rotation", "360° dynamic rotation"], ["Total Column Height", "3200 mm"], ["Bin Clamping Range", "300 L – 2000 L bins"], ["Safety Radius Clearance", "2500 mm perimeter envelope"], ["Net Weight", "Approx. 2200 kg"], ] }, fbd: { title: "Streamlined air-handling layout, direct granulation link.", desc: "Our Fluid Bed Dryer (FBD 120) features modular bowl docking for seamless gravity or vacuum transfer from upstream RMG mixers, with top-access filter bag inspection.", specs: [ ["Batch Capacity", "120 kg processing bowl"], ["Total Machine Height", "2850 mm"], ["Bowl Docking Clearance", "1100 mm floor clearance"], ["Required Room Height", "Minimum 3200 mm clearance"], ["Net Weight", "Approx. 1450 kg"], ] }, fbc: { title: "Vertical Wurster coating tower, ergonomic docking.", desc: "Our Fluid Bed Coater (FBC 300) delivers high precision bottom-spray coating with isolated operator zones, easy-access Wurster tube adjustment, and sanitary spray nozzle manifolds.", specs: [ ["Processing Capacity", "300 kg batch volume"], ["Total Unit Height", "3100 mm"], ["Wurster Column Clearance", "1200 mm servicing zone"], ["Required Room Height", "Minimum 3500 mm clearance"], ["Net Weight", "Approx. 1650 kg"], ] }, lpd: { title: "Ergonomic lifting mast, versatile bin positioning.", desc: "Our Lifting and Positioning Device features a rigid hydraulic/mechanical column with smooth 180°–360° swivel capability for dust-free blender and coater docking.", specs: [ ["Lifting Stroke Range", "1500 mm – 2400 mm"], ["Swivel Arc", "180° / 360° manual or powered"], ["Safe Working Load (SWL)", "Up to 500 kg+"], ["Required Room Height", "Minimum 3200 mm clearance"], ["Net Weight", "Approx. 850 kg"], ] }, vtd: { title: "Compact vacuum footprint, wide door swing radius.", desc: "Our Vacuum Tray Dryer is engineered with heavily reinforced vacuum chambers and uniform heating shelves, optimized for solvent extraction and heat-sensitive API drying.", specs: [ ["Chamber Dimensions", "2250 × 1550 mm footprint"], ["Door Swing Clearance", "1200 mm 90° swing arc"], ["Shelf Tray Spacing", "100 mm uniform shelf pitch"], ["Required Room Height", "Minimum 2700 mm clearance"], ["Net Weight", "Approx. 1950 kg"], ] }, "ipc-bin": { title: "Modular stacking envelope, sanitary funnel discharge.", desc: "Our IPC Bin Containers feature precision-formed 60° conical bases, stackable corner castings, and an ergonomic mobile trolley base for cleanroom transport.", specs: [ ["Bin Footprint", "1150 × 1150 mm base"], ["Overall Stacking Height", "1450 mm with lid"], ["Discharge Cone Angle", "60° sanitary frustum"], ["Valve Floor Clearance", "350 mm under-valve clearance"], ["Net Weight (Empty)", "Approx. 220 kg"], ] }, "eff-pro": { title: "Integrated processing skid, multi-vessel arrangement.", desc: "Our Effervescent Processing System (EFF-PRO 250) unifies formulation reactors, vacuum mixing, and condenser systems onto a single sanitary skid platform with dedicated operator access.", specs: [ ["Skid Footprint", "3200 × 1800 mm skid base"], ["Total Skid Height", "2450 mm top flange"], ["Sanitary Connection", "DN 100 tri-clamp ports"], ["Required Room Height", "Minimum 3000 mm clearance"], ["Net Weight", "Approx. 2800 kg"], ] }, "eff-pack": { title: "Continuous linear packaging line, high throughput.", desc: "Our EFF-PACK 100 tube filling line incorporates vibratory tablet sorting, automated tube indexing, desiccant cap insertion, and end-of-line collection in a compact linear cell.", specs: [ ["Line Envelope", "3600 × 1200 mm footprint"], ["Operating Speed", "100–120 tubes / minute"], ["Infeed Conveyor Height", "950 ± 50 mm working level"], ["Required Room Height", "Minimum 2600 mm clearance"], ["Net Weight", "Approx. 2100 kg"], ] } }; // ============ PRODUCT 3D VIEWER WITH LOADER ============ function ProductModelViewer({ src, alt }) { const [progress, setProgress] = useSP(0); const [loaded, setLoaded] = useSP(false); const [error, setError] = useSP(false); const viewerRef = React.useRef(null); useEP(() => { setProgress(0); setLoaded(false); setError(false); const el = viewerRef.current; if (!el) return; if (el.loaded) { setProgress(100); setLoaded(true); return; } const onProgress = (e) => { const p = Math.min(100, Math.max(0, Math.round((e.detail && e.detail.totalProgress ? e.detail.totalProgress : 0) * 100))); setProgress(p); }; const onLoad = () => { setProgress(100); setLoaded(true); }; const onError = () => { setError(true); }; el.addEventListener("progress", onProgress); el.addEventListener("load", onLoad); el.addEventListener("error", onError); return () => { el.removeEventListener("progress", onProgress); el.removeEventListener("load", onLoad); el.removeEventListener("error", onError); }; }, [src]); return ( LIVE 3D {error ? "3D model preview unavailable" : progress > 0 && progress < 100 ? `Loading model asset (${progress}%)...` : "Loading 3D model..."} {progress}% ); } // ============ PRODUCT HERO ============ function ProductHero() { const d = window.PRODUCT_DATA || {}; const [activeModelIdx, setActiveModelIdx] = useSP(0); const breadcrumb = d.breadcrumb || ["Home", "Products", "Granulation", "Rapid Mixer Granulator"]; const chips = d.chips || ["Flagship", "Granulation", "High-Shear"]; const name = d.name || "Rapid Mixer Granulator (RMG)"; const lede = d.description || "The Rapid Mixer Granulator (RMG) is engineered for fast, efficient, and uniform wet granulation across pharmaceutical, nutraceutical, food, and chemical manufacturing processes."; const subdesc = d.subDescription || "Manufactured in compliance with cGMP standards, our RMG offers robust construction, user-friendly operation, and reliable performance to meet the demands of modern production facilities."; const metrics = d.metrics || [["45–120", "Working L"], ["8–15", "mixing min"], ["SS 316L", "contact parts"]]; const modelsList = Array.isArray(d.models3d) && d.models3d.length > 0 ? d.models3d : null; const activeModel = modelsList ? (modelsList[activeModelIdx] || modelsList[0]) : null; const model3d = activeModel ? activeModel.src : (d.model3d || ""); const modelAlt = (activeModel && activeModel.alt) || d.modelAlt || `${name} 3D model`; const heroImage = d.heroImage || d.image || null; const brochureUrl = getProductBrochure(d); const gaDrawingUrl = getProductGADrawing(d); const hasHeroMedia = !!(model3d || heroImage); return ( {/* Breadcrumb */} {breadcrumb.map((b, idx) => ( {idx > 0 && /} {idx < breadcrumb.length - 1 ? ( {b} ) : ( {b} )} ))} {chips.map((c, i) => ( {c} ))} {name} {lede} {subdesc} {/* Metric strip */} {metrics.map(([n, l]) => ( {n} {l} ))} {gaDrawingUrl && ( View layout )} { window.__MODAL_CONTEXT__ = { action: "quote", title: "Request Equipment Specification & Proposal", badge: "Equipment Enquiry" }; window.dispatchEvent(new CustomEvent("show-lead-modal")); }} className="btn btn-pharma">Request quote Download brochure {hasHeroMedia && ( {model3d ? ( ) : heroImage ? ( ) : null} {model3d ? ( Live 3D explorer ) : heroImage ? ( Engineering Model ) : null} {modelsList && modelsList.length > 1 && ( {modelsList.map((m, idx) => ( setActiveModelIdx(idx)} style={{ border: "none", cursor: "pointer", padding: "6px 14px", borderRadius: "20px", fontFamily: "var(--font-mono, monospace)", fontSize: "11px", fontWeight: 600, letterSpacing: "0.04em", transition: "all 0.2s ease", background: activeModelIdx === idx ? "var(--ink, #0B1117)" : "transparent", color: activeModelIdx === idx ? "#ffffff" : "var(--ink-2, #475467)", }} > {m.label} ))} )} )} ); } // ============ PROCESS FLOW ============ function ProcessFlow() { const d = window.PRODUCT_DATA || {}; const [active, setActive] = useSP(0); const subtitle = d.processSubtitle || "Eight processing zones, high-shear, fully validated."; const steps = d.processSteps || [ { num: "01", label: "Dry charging", t: "Raw powder materials loaded into the mixing bowl via gravity, vacuum, or manually." }, { num: "02", label: "Dry mixing", t: "Impeller rotates at slow speed to homogenize and blend dry powders thoroughly." }, { num: "03", label: "Binder addition", t: "Liquid binder solution is sprayed or poured uniformly across the dry powder mix." }, { num: "04", label: "Wet mixing", t: "Impeller distributes binder solution to build a cohesive, consistent wet mass." }, { num: "05", label: "Granulation", t: "High-shear side-mounted chopper cuts the wet mass to form uniform granules." }, { num: "06", label: "Wet milling", t: "Optional online co-mill refines granule sizes and distribution before discharge." }, { num: "07", label: "Pneumatic discharge", t: "Automated discharge valve opens, transferring wet granules to the FBD bowl smoothly." }, { num: "08", label: "CIP / Washdown", t: "Easy access washdown with optional WIP/CIP spray balls for fast cleanups." }, ]; if (!steps || steps.length === 0) return null; return ( Process flow {subtitle} {/* Timeline */} {/* connector line */} {steps.map((s, i) => ( setActive(i)} style={{ background: "transparent", border: "none", textAlign: "left", cursor: "pointer", padding: "0 8px", display: "flex", flexDirection: "column", alignItems: "center" }}> = i ? "var(--pharma)" : "var(--line)"}`, background: active === i ? "var(--pharma)" : "white", color: active === i ? "white" : (active > i ? "var(--pharma)" : "var(--ink-3)"), display: "grid", placeItems: "center", fontFamily: "var(--font-mono)", fontSize: 11, fontWeight: 600, letterSpacing: "0.04em", position: "relative", zIndex: 1, transition: "all 200ms", }}> {s.num} {s.label} ))} {steps[active].num} · {steps[active].label} {steps[active].t} setActive(Math.max(0, active - 1))} className="btn btn-ghost" style={{ padding: "8px 12px", fontSize: 12 }}>← setActive(Math.min(steps.length - 1, active + 1))} className="btn btn-ghost" style={{ padding: "8px 12px", fontSize: 12 }}>→ ); } // ============ FEATURES GRID ============ function FeaturesGrid() { const d = window.PRODUCT_DATA || {}; const [showOptional, setShowOptional] = useSP(false); const subtitle = d.featuresSubtitle || "Engineered for high performance and process control."; const keyFeatures = d.keyFeatures || [ "Product contact parts manufactured in SS 316 / SS 316L", "High-shear impeller for rapid and uniform mixing", "Side-mounted chopper for efficient granulation", "Pneumatic discharge system for smooth material transfer", "cGMP-compliant design with easy access for cleaning and maintenance", "Robust construction with low maintenance requirements", "Dust-free processing with enhanced containment", "Safety interlocks for secure operation" ]; const optionalFeatures = d.optionalFeatures || [ "Vacuum charging system", "Binder solution preparation vessel", "Online wet milling system", "Explosion-proof execution", "CIP/WIP system", "OEB containment design", "SCADA integration" ]; const featuresToDisplay = showOptional ? [...keyFeatures, ...optionalFeatures] : keyFeatures; return ( Engineering Features {subtitle} {featuresToDisplay.map((feat, i) => { const isOptional = i >= keyFeatures.length; return ( { e.currentTarget.style.transform = "translateY(-4px)"; e.currentTarget.style.borderColor = isOptional ? "rgba(32,199,232,0.4)" : "var(--ink-3)"; e.currentTarget.style.background = isOptional ? "rgba(32,199,232,0.06)" : "#e2e8f0"; }} onMouseLeave={(e) => { e.currentTarget.style.transform = "none"; e.currentTarget.style.borderColor = "var(--line)"; e.currentTarget.style.background = isOptional ? "rgba(32,199,232,0.02)" : "var(--metal-3)"; }} > {isOptional ? "OPTIONAL FEATURE" : "KEY FEATURE"} {feat} ); })} setShowOptional(!showOptional)} style={{ padding: "14px 28px", fontSize: 14, borderRadius: "999px" }} > {showOptional ? "Hide Optional Features" : "Show Optional Features"} ); } // ============ ADVANTAGES SECTION ============ function AdvantagesSection() { const d = window.PRODUCT_DATA || {}; const advantages = d.advantages || [ { title: "Reduced processing time", desc: "Shortens dry mixing and wet granulation cycles to just 8-15 minutes, boosting overall throughput." }, { title: "Uniform size distribution", desc: "Delivers a consistent granule sizing with minimized fine particles, ideal for tablet compressing." }, { title: "Improved flow properties", desc: "Produces dense granules with excellent flowability, preventing downstream tableting hopper jams." }, { title: "Increased production efficiency", desc: "Automates charging and discharging to reduce manual processing, downtime, and operator labor." }, { title: "Reduced manual handling", desc: "Pneumatic systems and gravity feeds minimize operator contact with product powders." }, { title: "Consistent batch repeatability", desc: "Pre-programmed recipe cycles guarantee identical batch outputs, securing strict QA standards." }, ]; const title = d.advantagesTitle || "Engineering without compromise."; const desc = d.advantagesDesc || "Every Mark Maker machine is built on a foundation of rigorous pharmaceutical standards, leveraging modern automation to guarantee repeatability, yield, and operator safety."; // Carousel images configuration const rawCarousel = d.advantagesCarousel || (d.advantagesImage ? [{ src: d.advantagesImage, tag: "Engineering" }] : d.carouselItems); const carouselItems = (Array.isArray(rawCarousel) && rawCarousel.length > 0) ? rawCarousel.filter(item => item && item.src && !item.src.includes("placehold.co")) : null; const hasCarousel = !!(carouselItems && carouselItems.length > 0); const [currentSlide, setCurrentSlide] = React.useState(0); const prevSlide = () => { if (!hasCarousel) return; setCurrentSlide((prev) => (prev === 0 ? carouselItems.length - 1 : prev - 1)); }; const nextSlide = () => { if (!hasCarousel) return; setCurrentSlide((prev) => (prev === carouselItems.length - 1 ? 0 : prev + 1)); }; return ( {hasCarousel ? ( {/* Left Column: Advantages */} {title} {desc} {advantages.map((ad, idx) => ( {ad.title} {ad.desc} ))} {/* Right Column: Carousel Image */} {carouselItems.map((item, idx) => ( {item.tag && ( {item.tag} )} ))} {/* Navigation Controls (if multiple slides) */} {carouselItems.length > 1 && ( <> e.currentTarget.style.background = "#ffffff"} onMouseLeave={(e) => e.currentTarget.style.background = "rgba(255, 255, 255, 0.9)"} > e.currentTarget.style.background = "#ffffff"} onMouseLeave={(e) => e.currentTarget.style.background = "rgba(255, 255, 255, 0.9)"} > {/* Dots Indicator */} {carouselItems.map((_, idx) => ( setCurrentSlide(idx)} aria-label={`Go to slide ${idx + 1}`} style={{ width: currentSlide === idx ? 18 : 6, height: 6, borderRadius: "999px", background: currentSlide === idx ? "var(--pharma)" : "var(--metal)", border: "none", padding: 0, cursor: "pointer", transition: "all 200ms ease" }} /> ))} > )} ) : ( {title} {desc} {advantages.map((ad, idx) => ( {ad.title} {ad.desc} ))} )} ); } // ============ PRODUCT IMAGE GALLERY ============ function ProductGallery() { const d = window.PRODUCT_DATA || {}; const [images, setImages] = React.useState(d.galleryImages || []); const [selectedImg, setSelectedImg] = React.useState(null); const [isExpanded, setIsExpanded] = React.useState(false); const galleryDir = d.galleryDirectory; React.useEffect(() => { if (!galleryDir) return; fetch(galleryDir) .then((res) => { if (!res.ok) throw new Error("HTTP error " + res.status); return res.text(); }) .then((html) => { const parser = new DOMParser(); const doc = parser.parseFromString(html, "text/html"); const links = Array.from(doc.querySelectorAll("a")); const imgUrls = links .map((a) => a.getAttribute("href")) .filter((href) => href && /\.(png|jpe?g|webp|gif|svg)(\?.*)?$/i.test(href)) .map((href) => { const decodedName = decodeURIComponent(href.split("/").pop()); const fullUrl = href.startsWith("http") || href.startsWith("/") ? href : `${galleryDir.replace(/\/$/, "")}/${href}`; return { src: fullUrl, name: decodedName.replace(/\.[^/.]+$/, "").replace(/\s*\(EDITED\)/gi, "").trim() }; }); // Optimised .webp copies sit next to the original .png/.jpg files: // show each photo once, preferring the lightweight .webp. const byBase = new Map(); imgUrls.forEach((img) => { const base = img.src.replace(/\.[^/.?]+(\?.*)?$/, ""); if (!byBase.has(base) || /\.webp(\?.*)?$/i.test(img.src)) byBase.set(base, img); }); const uniqueImages = Array.from(byBase.values()); if (uniqueImages.length > 0) { setImages(uniqueImages); } }) .catch((err) => { console.warn("Could not dynamically fetch directory listing, falling back to static config:", err); }); }, [galleryDir]); if (!images || images.length === 0) return null; // Show 2 rows maximum when collapsed (4 items per row on desktop = 8 items) const initialCount = 8; const displayedImages = isExpanded ? images : images.slice(0, initialCount); const canExpand = images.length > initialCount; const isSingle = displayedImages.length === 1; const isCol4 = !isSingle && (d.galleryColumns === 3 || d.galleryColumns === "3" || d.galleryColClass === "col-4" || displayedImages.length === 3); const sectionLabel = d.galleryLabel || (isSingle ? "Visual Feature" : "Visual Showcase"); const defaultTitle = isSingle ? "Machine Showcase" : "Machine & Fabrication Gallery"; const title = d.galleryTitle || defaultTitle; const defaultDesc = isSingle ? "Explore detailed capture of our equipment build quality, sanitary finishes, and precision manufacturing." : "Explore detailed captures of our equipment build quality, sanitary finishes, and precision manufacturing."; const desc = d.galleryDesc || defaultDesc; return ( {sectionLabel} {title} {desc} {/* Gallery Grid */}
{lede}
{subdesc}
{desc}
{ad.desc}