// surgx-landing-v3.jsx — SurgX Landing page, design v3. // Mounts #root; IIFE pattern (same as browse-v3, Babel-race safe). // Blank-over-wrong throughout: every fetch failure omits the section, never fakes. // Reuses .sxv3 / .sx token root from surgx-pdp-v3.css; layout classes prefixed lx-. (() => { const { useState, useEffect, useRef, useCallback, useMemo } = React; /* ---- shared search core (surgx-search-core.js, loaded before this script) ---- */ const { str, normalizeVendor, matchesQuery } = window.SurgxSearchCore; /* ---- hero mode param (dark gate) ---- */ const _heroParam = new URLSearchParams(window.location.search).get('hero'); /* ================================================================ HeroConstellation — 3D point-cloud canvas of the real taxonomy. Hub nodes = specialties (Fibonacci sphere), system nodes clustered around each hub. Perspective projection + depth-fade. Auto-rotates ~1 rev/40s with pointer parallax on desktop. Renders ONLY when taxonomy is truthy AND ?hero=3d. prefers-reduced-motion: reduce → single static frame, no rAF. ================================================================ */ function HeroConstellation({ taxonomy }) { const canvasRef = useRef(null); const stateRef = useRef(null); useEffect(() => { if (!taxonomy || !taxonomy.specialties || !taxonomy.specialties.length) return; const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; /* ---- build 3D point cloud from taxonomy ---- */ const specs = taxonomy.specialties; const N = specs.length; const TAU = Math.PI * 2; const GOLDEN = (1 + Math.sqrt(5)) / 2; const HUB_R = 1.0; /* sphere radius for hub distribution */ /* Fibonacci sphere placement for hubs */ const hubs = []; const systems = []; for (let i = 0; i < N; i++) { const theta = Math.acos(1 - 2 * (i + 0.5) / N); const phi = TAU * i / GOLDEN; const hx = HUB_R * Math.sin(theta) * Math.cos(phi); const hy = HUB_R * Math.sin(theta) * Math.sin(phi); const hz = HUB_R * Math.cos(theta); const cnt = specs[i].count || 1; const r = Math.max(3, Math.min(8, Math.sqrt(cnt) * 1.4)); hubs.push({ x: hx, y: hy, z: hz, r: r, idx: i }); /* system nodes — jittered sphere around hub, capped at 14 */ const sysCount = Math.min(cnt, 14); for (let j = 0; j < sysCount; j++) { const st = Math.acos(1 - 2 * (j + 0.5) / Math.max(sysCount, 1)); const sp = TAU * j / GOLDEN; const spread = 0.12 + Math.random() * 0.06; const sx = hx + spread * Math.sin(st) * Math.cos(sp); const sy = hy + spread * Math.sin(st) * Math.sin(sp); const sz = hz + spread * Math.cos(st); systems.push({ x: sx, y: sy, z: sz, hub: i }); } } /* ---- rotation state ---- */ const rot = { ay: 0, targetAy: 0, ax: 0.25 }; /* fixed X tilt ~14deg */ const SPEED = TAU / 40; /* 1 rev per 40s */ let mouseInfluence = { dx: 0, dy: 0 }; let rafId = 0; let lastT = 0; let paused = false; /* ---- sizing (DPR-aware) ---- */ const dpr = Math.min(window.devicePixelRatio || 1, 2); function resize() { const rect = canvas.getBoundingClientRect(); canvas.width = rect.width * dpr; canvas.height = rect.height * dpr; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); } resize(); /* ---- projection helpers ---- */ function project(px, py, pz, w, h) { const cosAy = Math.cos(rot.ay); const sinAy = Math.sin(rot.ay); const cosAx = Math.cos(rot.ax); const sinAx = Math.sin(rot.ax); /* rotate Y then X */ const x1 = px * cosAy - pz * sinAy; const z1 = px * sinAy + pz * cosAy; const y1 = py * cosAx - z1 * sinAx; const z2 = py * sinAx + z1 * cosAx; const f = 2.8; const scale = f / (f + z2); return { sx: w * 0.5 + x1 * scale * w * 0.38, sy: h * 0.5 + y1 * scale * h * 0.38, scale: scale, z: z2 }; } /* ---- palette ---- */ const HUB_COLOR = '#0B4A72'; const SYS_COLOR = '#6FA6C6'; const EDGE_BASE = 'rgba(11,74,114,'; /* ---- frame ---- */ function frame(t) { if (paused) { rafId = requestAnimationFrame(frame); return; } const dt = lastT ? (t - lastT) / 1000 : 0.016; lastT = t; /* auto-rotate + mouse parallax with easing */ rot.targetAy += SPEED * dt; rot.ay += (rot.targetAy + mouseInfluence.dx - rot.ay) * 0.06; const rect = canvas.getBoundingClientRect(); const w = rect.width; const h = rect.height; if (canvas.width !== w * dpr || canvas.height !== h * dpr) resize(); ctx.clearRect(0, 0, w, h); /* project all points once */ const pH = hubs.map(p => project(p.x, p.y, p.z, w, h)); const pS = systems.map(p => project(p.x, p.y, p.z, w, h)); /* edges (hub → system), low alpha with depth — darkened for visibility on the white landing background (Drew walk 2026-06-10: lines too faint) */ for (let i = 0; i < pS.length; i++) { const s = pS[i]; const hProj = pH[systems[i].hub]; const depthAlpha = Math.max(0.10, Math.min(0.24, 0.24 * s.scale)); ctx.beginPath(); ctx.moveTo(hProj.sx, hProj.sy); ctx.lineTo(s.sx, s.sy); ctx.strokeStyle = EDGE_BASE + depthAlpha + ')'; ctx.lineWidth = 0.6; ctx.stroke(); } /* system nodes */ for (let i = 0; i < pS.length; i++) { const s = pS[i]; const alpha = Math.max(0.2, Math.min(0.85, 0.85 * s.scale)); const r = Math.max(0.8, 1.5 * s.scale); ctx.beginPath(); ctx.arc(s.sx, s.sy, r, 0, TAU); ctx.fillStyle = SYS_COLOR; ctx.globalAlpha = alpha; ctx.fill(); } /* hub nodes (on top) */ for (let i = 0; i < pH.length; i++) { const h2 = pH[i]; const alpha = Math.max(0.35, Math.min(1, 1.0 * h2.scale)); const r = Math.max(2, hubs[i].r * h2.scale); ctx.beginPath(); ctx.arc(h2.sx, h2.sy, r, 0, TAU); ctx.fillStyle = HUB_COLOR; ctx.globalAlpha = alpha; ctx.fill(); } ctx.globalAlpha = 1; rafId = requestAnimationFrame(frame); } /* ---- reduced motion: single static frame ---- */ const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches; if (prefersReduced) { rot.ay = 0.4; /* slight rotation so it's not head-on */ frame(0); /* no rAF loop */ stateRef.current = { cleanup: () => {} }; return; } rafId = requestAnimationFrame(frame); /* ---- pointer parallax (desktop only, no touch) ---- */ function onMouseMove(e) { const rect = canvas.getBoundingClientRect(); const nx = ((e.clientX - rect.left) / rect.width - 0.5) * 2; const ny = ((e.clientY - rect.top) / rect.height - 0.5) * 2; mouseInfluence.dx = nx * 0.3; } canvas.addEventListener('mousemove', onMouseMove); /* ---- visibility / intersection pause ---- */ function onVisChange() { paused = document.hidden; } document.addEventListener('visibilitychange', onVisChange); let observer = null; if (typeof IntersectionObserver !== 'undefined') { observer = new IntersectionObserver(([entry]) => { if (!entry.isIntersecting) paused = true; else if (!document.hidden) paused = false; }, { threshold: 0 }); observer.observe(canvas); } /* ---- cleanup ---- */ stateRef.current = { cleanup: () => { cancelAnimationFrame(rafId); canvas.removeEventListener('mousemove', onMouseMove); document.removeEventListener('visibilitychange', onVisChange); if (observer) observer.disconnect(); } }; return () => { if (stateRef.current && stateRef.current.cleanup) stateRef.current.cleanup(); }; }, [taxonomy]); if (!taxonomy || !taxonomy.specialties || !taxonomy.specialties.length) return null; return ( ); } /* ---- tiny helpers ---- */ const initials = n => { const w = String(n || '?').split(/\s+/).filter(Boolean); return ((w[0]?.[0] || '?') + (w[1]?.[0] || '')).toUpperCase(); }; /* ================================================================ SPECIALTY_GLYPHS — viewBox 0 0 24 24, monoline stroke, fill="none" Each value is an array of path-d strings (1-4 paths per glyph). ================================================================ */ const SPECIALTY_GLYPHS = { 'orthopedics': [ 'M12 3 L12 21', 'M9 3 Q12 5 15 3', 'M9 21 Q12 19 15 21' ], 'spine': [ 'M12 3 L12 21', 'M8 6 L16 6', 'M9 12 L15 12', 'M8 18 L16 18' ], 'neurosurgery': [ 'M12 4 Q5 4 5 12 Q5 20 12 20 Q19 20 19 12 Q19 4 12 4Z', 'M9 8 Q12 10 9 13 Q12 15 9 18', 'M15 8 Q12 10 15 14' ], 'cardiovascular': [ 'M12 20 L4 12 Q4 6 8 6 Q12 6 12 10 Q12 6 16 6 Q20 6 20 12Z' ], 'cardiothoracic': [ 'M12 18 L6 12 Q6 8 9 8 Q12 8 12 11 Q12 8 15 8 Q18 8 18 12Z', 'M3 10 Q5 14 3 18' ], 'vascular': [ 'M12 4 L12 12', 'M12 12 Q8 16 6 20', 'M12 12 Q16 16 18 20' ], 'general-plastic-surgery': [ 'M9 4 L15 4', 'M9 4 Q9 13 12 19', 'M15 4 Q15 13 12 19' ], 'ob-gyn': [ 'M12 7 Q7 7 7 13 L9 19', 'M12 7 Q17 7 17 13 L15 19', 'M9 19 L15 19' ], 'gi-urology': [ 'M15 3 L17 3', 'M15 3 Q15 8 11 10 Q6 12 6 16 Q6 20 10.5 20 Q15 20 16 15' ], 'ophthalmology': [ 'M3 12 Q12 5 21 12 Q12 19 3 12Z', 'M12 9.5 A2.5 2.5 0 0 1 12 14.5 A2.5 2.5 0 0 1 12 9.5Z' ], 'ent': [ 'M16 5 Q20 5 20 10 Q20 16 15 19', 'M16 10 Q14 10 14 13' ], 'dental': [ 'M8 4 Q6 4 6 8 Q6 11 8 12 Q9 14 9 18 L9 20', 'M16 4 Q18 4 18 8 Q18 11 16 12 Q15 14 15 18 L15 20', 'M8 4 Q12 6 16 4' ], 'anesthesiology': [ 'M7 8 Q7 4 12 4 Q17 4 17 8 L17 12 L7 12Z', 'M12 12 L12 20' ], 'critical-care': [ 'M4 12 L8 12 L10 7 L14 17 L16 12 L20 12' ], 'wound-care': [ 'M4 12 L20 12', 'M7 9 L9 15', 'M13 15 L11 9', 'M15 9 L17 15' ], 'radiology-imaging': [ 'M12 12 L12 12.01', 'M12 8 A4 4 0 0 1 16 12', 'M12 5 A7 7 0 0 1 19 12', 'M12 2 A10 10 0 0 1 22 12' ], 'oncology': [ 'M12 3 Q9 5 9 8 Q9 11 12 13 Q15 11 15 8 Q15 5 12 3Z', 'M10.5 12 Q13 15.5 16 20', 'M13.5 12 Q11 15.5 8 20' ], 'perioperative-or-support': [ 'M4 10 L20 10', 'M4 10 L4 8', 'M20 10 L20 8', 'M12 10 L12 18' ], 'blood-management': [ 'M12 4 Q7 11 7 14 Q7 20 12 20 Q17 20 17 14 Q17 11 12 4Z' ], 'diagnostics-lab': [ 'M9 4 L9 10 Q6 16 8 19 L16 19 Q18 16 15 10 L15 4', 'M9 4 L15 4' ], 'physical-medicine': [ 'M5 19 L12 12 L19 16', 'M9 15 A4.5 4.5 0 0 1 14.8 13.7' ], 'endocrinology': [ 'M12 4 Q8 9 8 13 Q8 18 12 18 Q16 18 16 13 Q16 9 12 4Z', 'M12 10 A1.5 1.5 0 0 1 12 13 A1.5 1.5 0 0 1 12 10Z' ], 'pediatrics-neonatology': [ 'M12 8 A3 3 0 0 1 12 2 A3 3 0 0 1 12 8Z', 'M12 8 Q8 12 9 18', 'M12 8 Q16 12 15 18' ], 'pulmonology': [ 'M12 3 L12 8', 'M11 9 Q6 11 6 15.5 Q6 20 9 20 Q11 20 11 16.5 L11 9', 'M13 9 Q18 11 18 15.5 Q18 20 15 20 Q13 20 13 16.5 L13 9' ], 'multispecialty': [ 'M12 4 L12 20', 'M4 12 L20 12', 'M6.3 6.3 L17.7 17.7', 'M17.7 6.3 L6.3 17.7' ], '__default': [ 'M7 7 L10 7 L10 10 L7 10Z', 'M14 7 L17 7 L17 10 L14 10Z', 'M7 14 L10 14 L10 17 L7 17Z', 'M14 14 L17 14 L17 17 L14 17Z' ] }; window.SurgxSpecialtyGlyphs = SPECIALTY_GLYPHS; /* ---- SpecialtyGlyph component ---- */ function SpecialtyGlyph({ slug }) { const paths = SPECIALTY_GLYPHS[slug] || SPECIALTY_GLYPHS['__default']; return ( ); } const Glyph = ({ name, size = 40 }) => (
Look up any surgical system — components, trays, and role-specific audio briefings — with every claim tied to its source.
A system can carry separate audio briefings — one for the surgeon, another for the circulating nurse, the CST, the first assist. The version you hear is the one made for your role.
{sample ? (