/* global React, Icons, Badge, Btn, Pill, Meter, Score, Stat, Eyebrow */ const { useState, useMemo } = React; /* ========================================================================= BUILD CONFIGURATOR — flagship interactive ========================================================================= */ const GEAR_CATALOG = { camera: [ { id: 'osmo-pocket-3', name: 'DJI Osmo Pocket 3', price: 519, draw: 4.2, weight: 179, score: 9.1, tag: 'IRL favorite' }, { id: 'sony-zv-1f', name: 'Sony ZV-1F', price: 449, draw: 3.8, weight: 229, score: 8.2, tag: null }, { id: 'gopro-13', name: 'GoPro Hero 13 Black', price: 399, draw: 5.5, weight: 154, score: 8.8, tag: 'Rugged' }, { id: 'iphone-only', name: 'iPhone (use yours)', price: 0, draw: 3.0, weight: 200, score: 7.4, tag: 'Free' }, ], audio: [ { id: 'rode-wgo3', name: 'Rode Wireless GO 3', price: 349, draw: 1.2, weight: 84, score: 9.0, tag: 'Lab pick' }, { id: 'dji-mic-2', name: 'DJI Mic 2', price: 349, draw: 1.4, weight: 91, score: 8.7, tag: null }, { id: 'shure-mv7', name: 'Shure MV7+', price: 279, draw: 2.0, weight: 550, score: 8.0, tag: 'Static' }, ], router: [ { id: 'peplink-br1', name: 'Peplink MAX BR1 Pro 5G',price: 1499, draw: 12, weight: 470, score: 9.4, tag: 'Pro' }, { id: 'gl-mt2500', name: 'GL.iNet Brume 2', price: 159, draw: 4, weight: 180, score: 7.6, tag: 'Budget' }, { id: 'liveu-solo-pro',name: 'LiveU Solo Pro', price: 1499, draw: 14, weight: 380, score: 9.2, tag: null }, { id: 'none-router', name: 'No router (tethered)', price: 0, draw: 0, weight: 0, score: 6.0, tag: 'Skip' }, ], power: [ { id: 'omni-20', name: 'Omnicharge Omni 20+', price: 199, draw: 0, weight: 510, capacity: 70, score: 8.8, tag: null }, { id: 'ec-river-2', name: 'EcoFlow River 2', price: 239, draw: 0, weight: 3500, capacity: 256, score: 8.6, tag: 'Stationary' }, { id: 'anker-737', name: 'Anker 737 PowerCore', price: 149, draw: 0, weight: 632, capacity: 86, score: 9.1, tag: 'Best value' }, { id: 'none-power', name: 'None (run off phone)', price: 0, draw: 0, weight: 0, capacity: 0, score: 5.0, tag: 'Risky' }, ], pack: [ { id: 'gunrun-v3', name: 'Gunrun Mark V backpack',price: 749, draw: 0, weight: 1800, score: 9.6, tag: 'Flagship' }, { id: 'peak-30', name: 'Peak Design Travel 30L',price: 299, draw: 0, weight: 1500, score: 7.9, tag: 'Generic' }, { id: 'sling', name: 'Just a sling bag', price: 89, draw: 0, weight: 400, score: 6.5, tag: 'Starter' }, ], }; const PRESETS = { starter: { name: 'Starter', budget: 500, audience: 'Beginner', env: 'Phone' }, mobile: { name: 'Mobile Creator', budget: 1500, audience: 'Beginner', env: 'Phone' }, travel: { name: 'Travel IRL', budget: 2500, audience: 'Upgrader', env: 'Travel' }, backpack: { name: 'Pro Backpack', budget: 4500, audience: 'Pro', env: 'Backpack' }, event: { name: 'Event Broadcast',budget: 6000, audience: 'Pro', env: 'Event' }, house: { name: 'StreamerHouse', budget: 9500, audience: 'Pro', env: 'Stationary' }, }; // The bonding/data market bifurcated into three lanes in 2025-26 — not a // single starter→pro ladder. Each lane is a different bet, not a different // budget. Builds are grouped by the lane they actually live in. const LANES = [ { id: 'vertical', cls: 'is-vertical', title: 'Phone · Vertical', tag: 'Osmo + Speedify lane', sub: 'Mobile-first, vertical-native, software bonding. TikTok Live / Kick.', presets: ['starter', 'mobile'], }, { id: 'sleeper', cls: 'is-sleeper', title: 'Sleeper · BELABOX', tag: 'BELABOX + Calyx lane', sub: 'DIY / open-source bonding, no monthly hardware tax. Mixed rigs.', presets: ['travel', 'house'], }, { id: 'prestige', cls: 'is-prestige', title: 'Prestige · LiveU', tag: 'Solo PRO + UnlimitedIRL lane', sub: 'Carrier-grade hardware, broadcast-grade SLA. Pays for itself at scale.', presets: ['backpack', 'event'], }, ]; const PRESET_PICKS = { starter: { camera: 'iphone-only', audio: 'shure-mv7', router: 'none-router', power: 'none-power', pack: 'sling' }, mobile: { camera: 'sony-zv-1f', audio: 'dji-mic-2', router: 'gl-mt2500', power: 'anker-737', pack: 'peak-30' }, travel: { camera: 'osmo-pocket-3', audio: 'rode-wgo3', router: 'gl-mt2500', power: 'omni-20', pack: 'peak-30' }, backpack: { camera: 'osmo-pocket-3', audio: 'rode-wgo3', router: 'peplink-br1', power: 'omni-20', pack: 'gunrun-v3' }, event: { camera: 'gopro-13', audio: 'rode-wgo3', router: 'liveu-solo-pro', power: 'omni-20', pack: 'gunrun-v3' }, house: { camera: 'osmo-pocket-3', audio: 'shure-mv7', router: 'peplink-br1', power: 'ec-river-2', pack: 'peak-30' }, }; const SLOTS = [ { key: 'camera', label: 'Camera', icon: }, { key: 'audio', label: 'Audio', icon: }, { key: 'router', label: 'Bonded uplink', icon: }, { key: 'power', label: 'Power', icon: }, { key: 'pack', label: 'Pack / mount', icon: }, ]; const PRESET_KEYS = Object.keys(PRESETS); const BuildPage = () => { const [presetId, setPresetId] = useState('backpack'); const [picks, setPicks] = useState(PRESET_PICKS.backpack); const [carriers, setCarriers] = useState(3); const [hours, setHours] = useState(4); const [bitrate, setBitrate] = useState(6); const applyPreset = (id) => { setPresetId(id); setPicks(PRESET_PICKS[id]); }; const swap = (slot, id) => { setPicks({ ...picks, [slot]: id }); }; // Roll up the selected items const items = SLOTS.map((s) => { const item = GEAR_CATALOG[s.key].find(g => g.id === picks[s.key]); return { slot: s, item }; }); const total = items.reduce((sum, it) => sum + (it.item?.price || 0), 0); const totalDraw = items.reduce((sum, it) => sum + (it.item?.draw || 0), 0); const totalWeight = items.reduce((sum, it) => sum + (it.item?.weight || 0), 0); const powerItem = items.find(i => i.slot.key === 'power').item; const powerCap = powerItem.capacity || 0; // Wh const runtime = totalDraw > 0 ? powerCap / totalDraw : 0; const avgScore = items.filter(i => i.item).reduce((s, i) => s + i.item.score, 0) / items.filter(i => i.item).length; // Data plan strategy const dataMonthly = 60 + carriers * 65 + (bitrate > 8 ? 40 : 0); const dataNeeded = bitrate * 0.45 * hours; return (
{/* HEADER ========================================================== */}
Flagship tool · Build Configurator v2.1

Configure your build.

Pick a preset, swap any component. The BOM, runtime, weight, and data strategy recalculate live. Every part is tested in our lab.

Live calc Catalog · Q2 ’26
BUILD ID · BC-{presetId.toUpperCase()}-{Math.abs(hash(JSON.stringify(picks))).toString(36).slice(0,5).toUpperCase()}
{/* PRESETS — three lanes, not a ladder ============================ */}
01 · Pick your lane

Three lanes, not a ladder.

The 2026 IRL market bifurcated. Each lane is a different bet — not a different budget tier. Pick the lane your stream actually lives in, then a starting build inside it.

Tip: swap any part after selecting
{LANES.map(lane => (
{lane.tag} {lane.presets.length} builds
{lane.title}

{lane.sub}

{lane.presets.map(k => ( ))}
))}
{/* MAIN BUILDER ==================================================== */}
{/* LEFT: slots ============================================== */}
02 · Bill of Materials
{/* Share build uses native Web Share API where available, falls back to clipboard. */} {/* Export PDF = browser print-to-PDF for now. Real PDF export with custom layout lands in Phase 2. */}
{SLOTS.map((slot) => { const item = GEAR_CATALOG[slot.key].find(g => g.id === picks[slot.key]); return ( swap(slot.key, id)} /> ); })} {/* Data plan sub-section */}
03 · Data strategy
Carrier mix & uplink budget
Quarterly verified
= 3 ? 'SIM injector' : carriers === 2 ? 'Dual-SIM router' : 'Hotspot + phone'} sub="See bonding guide" tone="accent" />
{[ { n: 'Verizon', plan: 'Business 5G Unl Plus', mbps: '15-50 ↑' }, { n: 'T-Mobile', plan: 'Business 5G Unl Adv', mbps: '20-100 ↑' }, { n: 'AT&T', plan: 'Business Unl Premium', mbps: '10-25 ↑' }, { n: 'Visible', plan: 'Plus (deprio at 50G)', mbps: '5-15 ↑' }, ].slice(0, carriers).map((c, i) => (
{c.n.toUpperCase()}
{c.plan}
{c.mbps}
))} {carriers < 4 && Array.from({ length: 4 - carriers }).map((_, i) => (
empty slot
))}
{/* RIGHT: live spec sheet ================================== */}
{/* mini-header */}
SPEC SHEET · LIVE

{PRESETS[presetId].name}

For: {PRESETS[presetId].audience} · {PRESETS[presetId].env}
{/* total */}
Build total {items.filter(i => i.item?.price > 0).length} parts
${total.toLocaleString()}
Affiliate disclosed · prices reflect typical street, May ’26
{/* sub-stats */}
} sub="lab-tested" /> i.item?.price > 0).length * 2.5).toFixed(0)} min`} sub="cold start" />
{/* runtime visual */}
Power budget = 4 ? 'var(--accent)' : runtime >= 2 ? 'var(--warn)' : 'var(--bad)' }}> {runtime >= 4 ? 'HEALTHY' : runtime >= 2 ? 'TIGHT' : 'INSUFFICIENT'}
= 4 ? '' : runtime >= 2 ? 'warn' : 'bad'} />
0h4h target8h+
{/* call-out */}
PRO TIP

{runtime < 2 ? 'Your draw outpaces your battery. Swap to the Omnicharge 20+ or add a second pack.' : totalDraw > 15 ? 'High draw — keep a 100W USB-PD wall in reach during cuts.' : 'Solid balance. Pack an extra cable for the router; it’s the #1 dead-stream cause we log.'}

{/* CTA */}
{/* Add-to-cart routes through the affiliate registry in Phase 5. For now: open an early-access mailto with the build summary. */} } href={`mailto:hello@livestreamers.org?subject=${encodeURIComponent('Build: ' + PRESETS[presetId].name + ' ($' + total + ')')}`}> Email me this build
window.print()}>Print PDF { const url = window.location.href; if (navigator.share) navigator.share({ title: 'My livestreamers.org build', url }); else navigator.clipboard?.writeText(url); }}>Share
{/* wiring diagram peek */}
Wiring diagram {/* Full interactive wiring diagram lands in Phase 3 — for now the SVG below is the preview. */} PREVIEW
{/* boxes */} {[['CAM', 30, 20], ['MIC', 30, 80], ['BAT', 290, 20], ['BAT', 290, 80]].map(([l, x, y], i) => ( {l} ))} ROUTER bonded {/* lines */}
{/* TEST LOG ========================================================= */}
Field-tested · Most recent runs for this build

From the lab log.

{/* "All test logs" lands when MDX runs ship in Phase 4. */}
{[ { loc: 'Downtown LA', date: 'May 02 ’26', drops: 0, bitrate: 6.2, dur: '4h 18m', notes: 'Heavy crowd, dropped to T-Mobile once.' }, { loc: 'Shibuya · Tokyo', date: 'Apr 24 ’26', drops: 2, bitrate: 5.4, dur: '3h 02m', notes: 'Convention floor, AT&T roaming spotty.' }, { loc: 'Suburban drive · TX', date: 'Apr 18 ’26', drops: 0, bitrate: 7.0, dur: '6h 41m', notes: 'Highway test, 4 carriers, all healthy.' }, ].map((t, i) => (
{t.date.toUpperCase()} {t.drops === 0 ? '0 drops' : `${t.drops} drops`}

{t.loc}

BITRATE {t.bitrate} Mbps DUR {t.dur}

{t.notes}

))}
); }; const SlotRow = ({ slot, selected, options, onChange }) => { const [open, setOpen] = useState(false); return (
{slot.icon}
{slot.label}
{selected?.name || 'Empty'}
{selected?.tag && {selected.tag}}
PRICE
${selected?.price || 0}
SCORE
{open && (
Alternatives · {options.length}
{options.map(o => ( ))}
)}
); }; const SliderControl = ({ label, value, setValue, min, max, step, unit }) => (
{label} {value}{unit && ` ${unit}`}
setValue(parseFloat(e.target.value))} />
); const Tile = ({ label, value, sub, tone }) => (
{label}
{value}
{sub &&
{sub}
}
); const SpecTile = ({ label, value, sub }) => (
{label}
{value}
{sub}
); function hash(s) { let h = 0; for (let i = 0; i < s.length; i++) h = ((h << 5) - h) + s.charCodeAt(i) | 0; return h; } window.BuildPage = BuildPage;