Skip to content

Commit 197741d

Browse files
committed
feat: enhance SOP with detailed instructions and add instructional media for assembly
1 parent 3921e1c commit 197741d

2 files changed

Lines changed: 208 additions & 1 deletion

File tree

site/public/docs/beatbox-assembly-sop.html

Lines changed: 208 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,26 @@
219219
background: var(--surface-2);
220220
}
221221
.subsection h3 { margin: 0 0 9px; font-size: 1rem; }
222+
.instruction-media {
223+
margin: 12px 0 14px;
224+
overflow: hidden;
225+
border: 1px solid var(--line);
226+
border-radius: 14px;
227+
background: white;
228+
}
229+
.instruction-media img {
230+
display: block;
231+
width: 100%;
232+
max-height: 560px;
233+
object-fit: contain;
234+
background: #f8fafc;
235+
}
236+
.instruction-media figcaption {
237+
padding: 9px 12px;
238+
color: var(--muted);
239+
font-size: .86rem;
240+
border-top: 1px solid var(--line);
241+
}
222242
.checklist { display: grid; gap: 8px; }
223243
.check-row {
224244
display: grid;
@@ -385,6 +405,7 @@ <h1 id="pageTitle"></h1>
385405
let metaState = loadJSON(META_KEY, {});
386406
let noteState = loadJSON(NOTE_KEY, {});
387407
let itemIndex = [];
408+
applySopEnhancements();
388409

389410
function loadJSON(key, fallback) {
390411
try { return JSON.parse(localStorage.getItem(key)) ?? fallback; } catch { return fallback; }
@@ -406,6 +427,191 @@ <h1 id="pageTitle"></h1>
406427
function renderList(list, el) {
407428
el.innerHTML = list.map(x => `<li>${escapeHTML(x)}</li>`).join('');
408429
}
430+
function applySopEnhancements() {
431+
const findSection = id => SOP.sections.find(section => section.id === id);
432+
const findSubsection = (sectionId, title) => findSection(sectionId)?.subsections?.find(subsection => subsection.title === title);
433+
const addUnique = (items, item, index = items?.length) => {
434+
if (!items || items.some(existing => existing.text === item.text)) return;
435+
items.splice(index, 0, item);
436+
};
437+
const replaceItemText = (items, from, to) => {
438+
if (!items) return;
439+
items.forEach(item => {
440+
if (item.text === from) item.text = to;
441+
});
442+
};
443+
const walkItems = cb => {
444+
SOP.sections.forEach(section => {
445+
if (section.items) section.items.forEach(item => cb(item, section));
446+
section.subsections?.forEach(subsection => subsection.items?.forEach(item => cb(item, section, subsection)));
447+
});
448+
};
449+
const replaceExactText = (from, to) => walkItems(item => {
450+
if (item.text === from) item.text = to;
451+
});
452+
453+
[
454+
'Soldering is required for the screen and IR nosepoke / IR barrier assemblies. This may be a practical limitation for users without soldering equipment or experience.',
455+
'Wall color remains an open tracking-design decision: white, black, and gray walls must be compared for tracking quality, reflections, contrast, and illumination stability before release.'
456+
].forEach(text => {
457+
if (!SOP.limitations.includes(text)) SOP.limitations.push(text);
458+
});
459+
460+
replaceExactText(
461+
'Keep the matte side of plexiglass facing outward and the glossy side inward unless a module-specific exception is documented.',
462+
'Keep the matte side of plexiglass facing inward to reduce internal reflections unless a module-specific exception is documented.'
463+
);
464+
replaceExactText(
465+
'Verify which surface is matte and which is glossy: matte side outward, glossy side inward.',
466+
'Verify which surface is matte and which is glossy: matte side inward to reduce reflections.'
467+
);
468+
replaceExactText(
469+
'Confirm all matte sides face outward.',
470+
'Confirm all matte sides face inward to reduce internal reflections.'
471+
);
472+
replaceExactText(
473+
'Use the same basic panel logic: matte side outward.',
474+
'Use the same basic panel logic: matte side inward.'
475+
);
476+
477+
const tools = SOP.sections.find(section => section.id === 'tools');
478+
if (tools?.items) {
479+
const hexTool = tools.items.find(item => item.text === 'Hex key / Allen key appropriate for M5 hardware');
480+
if (hexTool) hexTool.text = 'T-handle ball-end hex key, 2.5 mm';
481+
const forcepsText = 'Tissue forceps for clearing 3D-printed insert seats and placing inserts';
482+
if (!tools.items.some(item => item.text === forcepsText)) {
483+
tools.items.splice(4, 0, { text: forcepsText });
484+
}
485+
}
486+
487+
const materials = findSection('materials');
488+
if (materials?.subsections && !materials.subsections.some(subsection => subsection.title === 'PCB identifiers')) {
489+
materials.subsections.splice(2, 0, {
490+
title: 'PCB identifiers',
491+
items: [
492+
{ text: 'Feeder main PCB: BB_Feeder_main_V1 or BB_Feeder_main_V1.1' },
493+
{ text: 'IR light curtain PCB: BB_IR_light_curtain' },
494+
{ text: 'Feeder IR barrier PCB: BB_Feeder_IRBarrier_V1.1', level: 'todo', note: 'Revision/name to confirm.' },
495+
{ text: 'Raspberry Pi shield PCB: BB_RPi_shield_V1.1' },
496+
{ text: 'Lighting PCB: BP_Lighting_V1.1' }
497+
]
498+
});
499+
}
500+
501+
const safety = findSection('safety');
502+
addUnique(safety?.items, {
503+
text: 'When inserting M3 inserts, work over a flat, solid surface only. Never push the insert above a hand or body part; injury can occur if the tool slips.',
504+
level: 'critical'
505+
});
506+
addUnique(safety?.items, {
507+
text: 'Soldering is required for the screen and IR nosepoke / IR barrier assemblies; confirm the builder has appropriate soldering tools and experience before starting.',
508+
level: 'critical'
509+
});
510+
511+
const prebuild = findSection('prebuild');
512+
addUnique(prebuild?.items, {
513+
text: 'Confirm the exact PCB revisions before assembly: BB_Feeder_main_V1 / V1.1, BB_IR_light_curtain, BB_Feeder_IRBarrier_V1.1, BB_RPi_shield_V1.1, and BP_Lighting_V1.1.',
514+
level: 'critical'
515+
}, 2);
516+
addUnique(prebuild?.items, {
517+
text: 'Confirm soldering capability and required soldering steps for the screen and IR nosepoke / IR barrier assemblies.',
518+
level: 'critical'
519+
}, 4);
520+
521+
const partPrepIntegrity = findSubsection('part_prep', '2. Verify printed-part integrity');
522+
addUnique(partPrepIntegrity?.items, {
523+
text: 'Use tissue forceps to clean small 3D-printing imperfections and to open insert entries before placing inserts.',
524+
level: 'caution'
525+
});
526+
addUnique(partPrepIntegrity?.items, {
527+
text: 'Start each insert by pushing gently with a thumb only to initiate entry, then use the 2.5 mm T-handle ball-end hex key to drive it in straight.',
528+
level: 'critical'
529+
});
530+
531+
const stageA = findSection('stage_a');
532+
const basePrep = stageA?.subsections?.find(subsection => subsection.title === '3. Prepare the base');
533+
if (basePrep) {
534+
basePrep.media = {
535+
src: '/videos/buidling_gifs/BEATBox_foot1.gif',
536+
alt: 'Installing the BEATBox base foot assembly',
537+
caption: 'Base and foot assembly reference.'
538+
};
539+
}
540+
541+
const pillars = findSubsection('stage_a', '4. Prepare the pillars');
542+
addUnique(pillars?.items, {
543+
text: 'Use tissue forceps to clear insert/nut seats and start inserts cleanly before driving them with the 2.5 mm T-handle ball-end hex key.',
544+
level: 'caution'
545+
});
546+
addUnique(pillars?.items, {
547+
text: 'Place the pillar on a flat, solid surface before applying force to inserts; never brace the part against your hand or body.',
548+
level: 'critical'
549+
});
550+
551+
const wallBuild = findSubsection('stage_b', '6. Build each plexiglass sandwich panel');
552+
addUnique(wallBuild?.items, {
553+
text: 'Resolve wall color for tracking before production: compare white, black, and gray for tracking contrast, reflections, lighting stability, and compatibility with the camera/IR setup.',
554+
level: 'todo',
555+
note: 'Decision still open.'
556+
});
557+
558+
const screenPreparation = findSubsection('screen_module', 'Preparation');
559+
addUnique(screenPreparation?.items, {
560+
text: 'Confirm soldering is complete or planned for the screen assembly before closing the module.',
561+
level: 'critical'
562+
});
563+
addUnique(screenPreparation?.items, {
564+
text: 'Define and document the optimal cable length: cables must be neither too long nor too short, with enough slack for connection but no loops that interfere with closing or mounting.',
565+
level: 'todo'
566+
});
567+
568+
const screenAssembly = findSubsection('screen_module', 'Assembly');
569+
if (screenAssembly?.items) {
570+
replaceItemText(screenAssembly.items, 'Install required M3 inserts.', 'Install required M3 inserts using tissue forceps to start the entry and the 2.5 mm T-handle ball-end hex key to drive them in straight.');
571+
replaceItemText(screenAssembly.items, 'Mount the screen PCB.', 'Screw the screen alone into the first housing part.');
572+
replaceItemText(screenAssembly.items, 'Insert the screen in the intended orientation.', 'Place and route the cables before joining the two housing/electronic parts.');
573+
replaceItemText(screenAssembly.items, 'Connect the screen properly to the board.', 'Connect the two parts, checking connector orientation and cable strain before tightening.');
574+
replaceItemText(screenAssembly.items, 'Add the cover/front face.', 'Screw the second housing part only after cables are seated, strain-free, and clear of the closing surfaces.');
575+
addUnique(screenAssembly.items, {
576+
text: 'Verify cable length after closure: not too short to pull connectors, not too long to create loops or pinching. Define the optimal length and preferred routing solution for the release.',
577+
level: 'todo'
578+
});
579+
}
580+
581+
const screenIr = findSubsection('screen_module', 'IR barrier on screen-side module');
582+
addUnique(screenIr?.items, {
583+
text: 'Confirm soldering requirements for IR nosepoke / IR barrier elements before mounting.',
584+
level: 'critical'
585+
});
586+
587+
const ceilingAssembly = findSubsection('ceiling_module', 'Assembly');
588+
replaceItemText(ceilingAssembly?.items, 'Install inserts as needed.', 'Install inserts as needed, using tissue forceps to clean the entry and the 2.5 mm T-handle ball-end hex key to drive inserts in straight over a flat, solid surface.');
589+
590+
const tunnelItems = findSection('tunnel_module')?.items;
591+
replaceItemText(tunnelItems, 'Install inserts.', 'Install inserts, using tissue forceps to clean the entry and the 2.5 mm T-handle ball-end hex key to drive inserts in straight over a flat, solid surface.');
592+
593+
const feederPrep = findSubsection('feeder_module', 'Preparation');
594+
addUnique(feederPrep?.items, {
595+
text: 'Confirm whether BB_Feeder_main_V1 or BB_Feeder_main_V1.1 is used for this build.',
596+
level: 'critical'
597+
}, 1);
598+
addUnique(feederPrep?.items, {
599+
text: 'Confirm BB_Feeder_IRBarrier_V1.1 sensor hardware and any required soldering are complete.',
600+
level: 'critical'
601+
});
602+
603+
const feederMechanical = findSubsection('feeder_module', 'Mechanical assembly');
604+
replaceItemText(feederMechanical?.items, 'Install M3 inserts in the feeder housing.', 'Install M3 inserts in the feeder housing, using tissue forceps to clean/start the entry and the 2.5 mm T-handle ball-end hex key to drive inserts in straight over a flat, solid surface.');
605+
addUnique(feederMechanical?.items, {
606+
text: 'For feeder IR sensors, physically remove the small black locating pin with scissors or tissue forceps when it prevents flat contact with the wall surface.',
607+
level: 'critical'
608+
}, 2);
609+
}
610+
function renderMedia(media) {
611+
if (!media?.src) return '';
612+
const caption = media.caption ? `<figcaption>${escapeHTML(media.caption)}</figcaption>` : '';
613+
return `<figure class="instruction-media"><img src="${escapeHTML(media.src)}" alt="${escapeHTML(media.alt || '')}" loading="lazy" />${caption}</figure>`;
614+
}
409615
function renderItems(items, section, path='') {
410616
if (!items || !items.length) return '';
411617
return `<div class="checklist">${items.map(item => {
@@ -446,6 +652,7 @@ <h1 id="pageTitle"></h1>
446652
<div class="subsection">
447653
<h3>${escapeHTML(sub.title)}</h3>
448654
${sub.intro ? `<p class="intro">${escapeHTML(sub.intro)}</p>` : ''}
655+
${renderMedia(sub.media)}
449656
${renderItems(sub.items || [], section, sub.title)}
450657
</div>`).join('');
451658
} else {
@@ -627,4 +834,4 @@ <h3>${escapeHTML(sub.title)}</h3>
627834
init();
628835
</script>
629836
</body>
630-
</html>
837+
</html>
23.5 MB
Loading

0 commit comments

Comments
 (0)