// 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');
/* ---- public-site refresh v2 (spec 2026-07-18 v3.1; flag SX_PUBLIC_SITE_REFRESH_V2) ---- */
const _refreshV2 = (window.SX_PUBLIC_SITE_REFRESH_V2 === true);
/* §2 FROZEN chips — exactly 3, each with declared behavior + sourcing:
* (B) drapes technique chip: NON-submitting affordance, opens the flag-ON
* Topology page (visible preview label) — the substring matcher cannot
* answer a technique query, so it is never submitted into Landing search.
* (A) two chips PROVEN non-empty under matchesQuery() over real R2 fields:
* 'hologic myosure' → display_name 'Hologic MyoSure' (curated record)
* 'tourniquet' → display_name/slug 'cat-gen7-tourniquet' (curated record)
* Proof commands + output are in the build PR body (m3-13). */
const REFRESH_CHIPS = [
{ label: 'bilateral lower extremity drapes', kind: 'topology', dot: '#1B1A18' },
{ label: 'hologic myosure', kind: 'search', dot: '#5A6E5C' },
{ label: 'tourniquet', kind: 'search', dot: '#A05F47' },
];
function RefreshChips({ onSearchChip }) {
return (
{REFRESH_CHIPS.map(c =>
c.kind === 'topology' ? (
{c.label}
preview
) : (
onSearchChip(c.label)}
>
{c.label}
)
)}
Direct hit · system record · statistical answer with receipts · labeled trend
);
}
/* ================================================================
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 (
{paths.map((d, i) => (
))}
);
}
const Glyph = ({ name, size = 40 }) => (
{initials(name)}
);
/* ---- clearance chip (same 3-state vocabulary as pdp-v3 / browse-v3) ---- */
/* NOTE: duplicated from browse-v3.jsx — both files share this component.
If you change ClearanceChip behavior, update BOTH files. */
function ClearanceChip({ fda_510k_primary }) {
const k = (str(fda_510k_primary).match(/^(K\d{6})$/) || [])[1];
if (k) return FDA 510(k) {k} ;
return no K-number on record ;
}
/* ================================================================
Typeahead — dual-tier search: curated first, then generic, top 8
================================================================ */
function Typeahead({ curated, generic, externalQuery }) {
const [q, setQ] = useState('');
const [focused, setFocused] = useState(false);
const inputRef = useRef(null);
const wrapRef = useRef(null);
/* (A)-chip wiring: an externally-supplied proven query lands in the box */
useEffect(() => {
if (externalQuery) {
setQ(externalQuery);
setFocused(true);
if (inputRef.current) inputRef.current.focus();
}
}, [externalQuery]);
/* close on outside click */
useEffect(() => {
const handler = e => { if (wrapRef.current && !wrapRef.current.contains(e.target)) setFocused(false); };
document.addEventListener('mousedown', handler);
return () => document.removeEventListener('mousedown', handler);
}, []);
const results = useMemo(() => {
const term = q.trim().toLowerCase();
if (!term) return [];
const curatedHits = (curated || []).filter(s => matchesQuery(s, term)).map(s => ({ ...s, _tier: 'curated' }));
const genericHits = (generic || []).filter(s => matchesQuery(s, term)).map(s => ({ ...s, _tier: 'generic' }));
/* curated first, then generic, capped at 8 total */
const combined = [];
for (const s of curatedHits) { if (combined.length >= 8) break; combined.push(s); }
for (const s of genericHits) { if (combined.length >= 8) break; combined.push(s); }
return combined;
}, [q, curated, generic]);
const showDropdown = focused && q.trim().length > 0;
const navigate = useCallback((slug) => {
window.location.href = '/system/' + encodeURIComponent(slug);
}, []);
const goToBrowse = useCallback(() => {
const term = q.trim();
if (term) {
window.location.href = '/browse?q=' + encodeURIComponent(term);
} else {
window.location.href = '/browse';
}
}, [q]);
const onKeyDown = useCallback((e) => {
if (e.key === 'Enter') {
e.preventDefault();
goToBrowse();
}
}, [goToBrowse]);
return (
setQ(e.target.value)}
onFocus={() => setFocused(true)}
onKeyDown={onKeyDown}
placeholder="Search systems, vendors, K-numbers..."
aria-label="Search the dictionary"
/>
{showDropdown ? (
{results.length > 0 ? (
<>
{results.map(s => (
navigate(s.slug)}
>
{s.display_name || s.slug}
{normalizeVendor(s.vendor)}
{s._tier === 'curated' ? 'curated · grounded' : 'FDA records only'}
))}
see all results for "{q.trim()}"
>
) : (
no matches — browse the full dictionary
)}
) : null}
);
}
/* ================================================================
Featured card (browse-card idiom, .sx-card + glyph + chips)
================================================================ */
/* NOTE: duplicated browse-card pattern from browse-v3.jsx.
If you change the card layout, update BOTH files. */
function FeaturedCard({ slug, display_name, vendor, fda_510k_primary, has_audio }) {
const name = display_name || slug;
const vendorShort = normalizeVendor(vendor);
return (
{name}
{vendorShort ?
{vendorShort}
: null}
curated · grounded
{has_audio ? audio : null}
);
}
/* ================================================================
Root component
================================================================ */
function SurgxLandingV3Root() {
/* fetch states — null = loading, false = failed */
const [landing, setLanding] = useState(null);
const [taxonomy, setTaxonomy] = useState(null);
const [systems, setSystems] = useState(null);
const [genericIndex, setGenericIndex] = useState(null);
const [sampleErr, setSampleErr] = useState(false);
const [chipQuery, setChipQuery] = useState('');
/* independent fetches — each error-tolerant */
useEffect(() => {
fetch('/api/r2/landing')
.then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
.then(setLanding)
.catch(() => setLanding(false));
fetch('/api/r2/taxonomy')
.then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
.then(setTaxonomy)
.catch(() => setTaxonomy(false));
fetch('/api/r2/systems')
.then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
.then(setSystems)
.catch(() => setSystems(false));
fetch('/api/r2/generic-index')
.then(r => { if (!r.ok) return false; return r.json(); })
.then(d => setGenericIndex(d || false))
.catch(() => setGenericIndex(false));
}, []);
/* derived lists for typeahead */
const curatedList = systems && systems.items ? systems.items : [];
const genericList = genericIndex && genericIndex.items ? genericIndex.items : [];
/* landing payload sub-objects */
const stats = landing && landing.stats ? landing.stats : null;
const featured = landing && landing.featured && landing.featured.length > 0 ? landing.featured : null;
const sample = landing && landing.sample ? landing.sample : null;
return (
{/* ============================================================
1. HERO
============================================================ */}
{/* official wordmark (Drew walk annotation 2026-06-10): content-bbox
crop + downscale of static/og/surgx-logo.png (alpha preserved) */}
{_refreshV2 ? (
<>
Surgical-supply intelligence · surgx.io
A cited archive of the surgical supply.
{stats ? (
<>{stats.total_systems.toLocaleString('en-US')} systems, structured as an ontology you can search, browse, and walk. Every claim carries its source. Every gap is disclosed.>
) : (
<>Structured as an ontology you can search, browse, and walk. Every claim carries its source. Every gap is disclosed.>
)}
>
) : (
<>
Surgical supply, with receipts.
Look up any surgical system — components, trays, and role-specific audio briefings — with every claim tied to its source.
>
)}
{_refreshV2 ?
: null}
{_heroParam === '3d' ? : null}
{/* ============================================================
2. SPECIALTY GRID
============================================================ */}
{taxonomy && taxonomy.specialties && taxonomy.specialties.length > 0 ? (
) : null}
{/* ============================================================
3. RECEIPTS BAND — numbers from /api/r2/landing stats only
============================================================ */}
{stats ? (
the receipts
{/* every card links to a TRUE destination (Drew walk annotation).
On-page anchors render as links only while their target section
exists — degraded fetch states fall back to a plain card (no
dead anchors; Bugbot catch on #1686). */}
) : null}
{/* ============================================================
4. FEATURED ROW
============================================================ */}
{featured ? (
featured systems
{featured.map(s => (
))}
) : null}
{/* ============================================================
5. EDUCATION HOOK — role-targeted audio
============================================================ */}
{(sample || (stats && stats.audio_tracks)) && !sampleErr ? (
role-targeted audio
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 ? (
{sample.title}
setSampleErr(true)}
>
Your browser does not support the audio element.
) : null}
) : null}
{/* ============================================================
6. GROUNDING STRIP
============================================================ */}
how this is built
FDA records first
Profiles anchor to FDA premarket records — 510(k), PMA, De Novo — wherever one exists, and every cited record is verified against openFDA. Marketing copy is not a source. Systems with no premarket record on file say so.
Source documents linked on every page
Where a fact came from is shown alongside the fact. Cited K-numbers link to the FDA record; gaps are labeled as gaps.
Missing is shown as missing
If a field has no verified source, it says so. Nothing is invented to fill a blank.
{/* ============================================================
7. FOOTER
============================================================ */}
);
}
window.SurgxLandingV3Root = SurgxLandingV3Root;
// Mount inside the transpiled script (Babel-race pattern: a separate
// DOMContentLoaded mount blanks the page — keep this inline mount).
ReactDOM.createRoot(document.getElementById('root')).render( );
})();