CoolFace
Apppublic

pollen-robotics/Reachy_Mini_LITE_Assembly_Guide

sourceHugging Faceupdated 7mo agoView on Hugging Face
10likes
script.js426 linesDownload Raw Back to root
1// Assembly Steps Data2const YOUTUBE_VIDEO_ID = "PC5Yx950nMY";3 4const stepsData = [5    { timestamp: "00:14", title: "Set all parts aside" },6    { timestamp: "00:30", title: "Stick Foot Pads" },7    { timestamp: "01:18", title: "Fix the USB Extension Cable and add the protective sleeve" },8    { timestamp: "02:58", title: "Insert the Power Board" },9    { timestamp: "03:17", title: "Fix the Power Board" },10    { timestamp: "04:06", title: "Connect the cables to the Power Board" },11    { timestamp: "05:26", title: "Position the Bottom Assembly" },12    { timestamp: "07:20", title: "Fix the Foot Assembly to the Bottom Assembly" },13    { timestamp: "08:15", title: "Connect the Foot Motor" },14    { timestamp: "09:15", title: "Screw the link rods onto the motor arms" },15    { timestamp: "12:22", title: "Connect Motor 1 to 2" },16    { timestamp: "13:11", title: "Connect Motor 2 to 3" },17    { timestamp: "13:36", title: "Connect Motor 4 to 5" },18    { timestamp: "13:55", title: "Connect Motor 5 to 6" },19    { timestamp: "14:24", title: "Insert all motors into the Bottom Assembly" },20    { timestamp: "15:54", title: "Clip the motor's cables into the Bottom Assembly" },21    { timestamp: "19:00", title: "Position the Tricap" },22    { timestamp: "19:19", title: "Route the cables" },23    { timestamp: "19:51", title: "Connect Motor 3 to 4" },24    { timestamp: "20:40", title: "Route the cables" },25    { timestamp: "22:02", title: "Check rotation" },26    { timestamp: "22:24", title: "Screw the Tricap" },27    { timestamp: "23:56", title: "Screw the Bottom Head onto the link rods" },28    { timestamp: "27:50", title: "Route the cables through the Bottom Head" },29    { timestamp: "28:45", title: "Connect the flexible camera cable (step for reference only)" },30    { timestamp: "28:51", title: "Route the cables through the Head PCB" },31    { timestamp: "29:16", title: "Screw the Head PCB" },32    { timestamp: "30:19", title: "Position the Top Shell" },33    { timestamp: "30:53", title: "Screw the Top Shell" },34    { timestamp: "32:18", title: "Place the lenses in the Glasses Holder" },35    { timestamp: "32:30", title: "Insert the Fisheye Lenses into the caps" },36    { timestamp: "32:54", title: "Snap the Fisheye Lenses" },37    { timestamp: "33:39", title: "Screw the Glasses Assembly onto the Front Head Shell" },38    { timestamp: "34:40", title: "Attach the cases to Antenna's motors" },39    { timestamp: "34:59", title: "Fix Antenna's Assembly to Back Head Shell" },40    { timestamp: "36:30", title: "Connect the Antenna's motors" },41    { timestamp: "36:52", title: "Slide the Back Head Assembly onto the Reachy Mini body" },42    { timestamp: "37:14", title: "Screw the Back Head" },43    { timestamp: "38:00", title: "Fix the Cable Holder" },44    { timestamp: "39:45", title: "Connect Speaker and Motor Cables" },45    { timestamp: "40:07", title: "Connect Power and USB Extension Cables" },46    { timestamp: "40:25", title: "Slide the Top Head Assembly onto the Back Head" },47    { timestamp: "40:39", title: "Connect the Flexible Printed Cable to the Head PCB" },48    { timestamp: "40:50", title: "Connect the Flexible Camera Cable on the Front Head" },49    { timestamp: "41:42", title: "Fix the Front Head" },50    { timestamp: "42:56", title: "Fix the Antennas" },51    { timestamp: "43:42", title: "Turn it on!" },52];53 54// Parse timestamp to seconds (MM:SS format)55function parseTimestamp(ts) {56    const parts = ts.split(':').map(Number);57    return parts[0] * 60 + parts[1];58}59 60// Create assembly steps with parsed timestamps61const assemblySteps = stepsData.map((step, index) => ({62    id: index + 1,63    title: step.title,64    timestamp: step.timestamp,65    timestampSeconds: parseTimestamp(step.timestamp)66}));67 68const TOTAL_STEPS = assemblySteps.length;69 70// Available step images71const availableImages = {72    1: "assets/step1.jpg",73    2: "assets/step2.jpg",74    3: "assets/step3.jpg",75    4: "assets/step4.jpg",76    5: "assets/step5.jpg",77    6: "assets/step6.jpg",78    7: "assets/step7.jpg",79    8: "assets/step8.jpg",80    9: "assets/step9.jpg",81    10: "assets/step10.jpg",82    11: "assets/step11.jpg",83    12: "assets/step12.jpg",84    13: "assets/step13.jpg",85    14: "assets/step14.jpg",86    15: "assets/step15.jpg",87    16: "assets/step16.jpg",88    17: "assets/step17.jpg",89    18: "assets/step18.jpg",90    19: "assets/step19.jpg",91    20: "assets/step20.jpg",92    21: "assets/step21.jpg",93    22: "assets/step22.jpg",94    23: "assets/step23.jpg",95    24: "assets/step24.jpg",96    25: "assets/step25.jpg",97    26: "assets/step26.jpg",98    27: "assets/step27.jpg",99    28: "assets/step28.jpg",100    29: "assets/step29.jpg",101    30: "assets/step30.jpg",102    31: "assets/step31.jpg",103    32: "assets/step32.jpg",104    33: "assets/step33.jpg",105    34: "assets/step34.jpg",106    35: "assets/step35.jpg",107    36: "assets/step36.jpg",108    37: "assets/step37.jpg",109    38: "assets/step38.jpg",110    39: "assets/step39.jpg",111    40: "assets/step40.jpg",112    41: "assets/step41.jpg",113    42: "assets/step42.jpg",114    43: "assets/step43.jpg",115    44: "assets/step44.jpg",116    45: "assets/step45.jpg",117    46: "assets/step46.jpg",118    47: "assets/step47.jpg",119};120 121function getStepImage(stepId) {122    return availableImages[stepId] || null;123}124 125// App State126let currentStep = 1;127let isFullscreen = false;128let scale = 1;129let position = { x: 0, y: 0 };130let isDragging = false;131let dragStart = { x: 0, y: 0 };132 133// DOM Elements134const stepCounterText = document.getElementById('step-counter-text');135const stepImage = document.getElementById('step-image');136const placeholder = document.getElementById('placeholder');137const placeholderNumber = document.getElementById('placeholder-number');138const imageWrapper = document.getElementById('image-wrapper');139const youtubeIframeDesktop = document.getElementById('youtube-iframe-desktop');140const youtubeIframeMobile = document.getElementById('youtube-iframe-mobile');141const prevBtn = document.getElementById('prev-btn');142const nextBtn = document.getElementById('next-btn');143const stepIndicators = document.getElementById('step-indicators');144const progressBar = document.getElementById('progress-bar');145const fullscreenBtn = document.getElementById('fullscreen-btn');146const fullscreenModal = document.getElementById('fullscreen-modal');147const closeFullscreenBtn = document.getElementById('close-fullscreen-btn');148const fullscreenStepTitle = document.getElementById('fullscreen-step-title');149const fullscreenImage = document.getElementById('fullscreen-image');150const fullscreenPlaceholder = document.getElementById('fullscreen-placeholder');151const fullscreenPlaceholderNumber = document.getElementById('fullscreen-placeholder-number');152const fullscreenImageContainer = document.getElementById('fullscreen-image-container');153const fullscreenYoutubeIframeDesktop = document.getElementById('fullscreen-youtube-iframe-desktop');154const fullscreenYoutubeIframeMobile = document.getElementById('fullscreen-youtube-iframe-mobile');155const fullscreenPrevBtn = document.getElementById('fullscreen-prev-btn');156const fullscreenNextBtn = document.getElementById('fullscreen-next-btn');157const fullscreenStepIndicators = document.getElementById('fullscreen-step-indicators');158const fullscreenProgressBar = document.getElementById('fullscreen-progress-bar');159const zoomInBtn = document.getElementById('zoom-in-btn');160const zoomOutBtn = document.getElementById('zoom-out-btn');161const zoomLevel = document.getElementById('zoom-level');162 163// Update YouTube embed164function updateYouTubeEmbed(timestampSeconds) {165    const embedUrl = `https://www.youtube.com/embed/${YOUTUBE_VIDEO_ID}?start=${timestampSeconds}&rel=0&autoplay=1&mute=1`;166    youtubeIframeDesktop.src = embedUrl;167    youtubeIframeMobile.src = embedUrl;168    fullscreenYoutubeIframeDesktop.src = embedUrl;169    fullscreenYoutubeIframeMobile.src = embedUrl;170}171 172// Render step indicators with pagination173function renderStepIndicators(containerId, currentStep, onClick) {174    const container = document.getElementById(containerId);175    container.innerHTML = '';176    177    const groupStart = Math.floor((currentStep - 1) / 10) * 10 + 1;178    const groupEnd = Math.min(groupStart + 9, TOTAL_STEPS);179    const canGoPrevGroup = groupStart > 1;180    const canGoNextGroup = groupEnd < TOTAL_STEPS;181    182    // Previous group button183    if (canGoPrevGroup) {184        const prevGroupBtn = document.createElement('button');185        prevGroupBtn.className = 'step-indicator-nav';186        prevGroupBtn.innerHTML = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 18l-6-6 6-6"/></svg>';187        prevGroupBtn.setAttribute('aria-label', 'Previous group');188        prevGroupBtn.addEventListener('click', () => onClick(groupStart - 1));189        container.appendChild(prevGroupBtn);190    }191    192    // Step number buttons    193    for (let i = 0; i < 10; i++) {194        const stepNum = groupStart + i;195        if (stepNum > TOTAL_STEPS) break;196        197        const isActive = stepNum === currentStep;198        const button = document.createElement('button');199        button.className = `step-indicator-num ${isActive ? 'step-indicator-num-active' : ''}`;200        button.textContent = stepNum;201        button.setAttribute('aria-label', `Go to step ${stepNum}`);202        button.addEventListener('click', () => onClick(stepNum));203        container.appendChild(button);204    }205    206    // Next group button207    if (canGoNextGroup) {208        const nextGroupBtn = document.createElement('button');209        nextGroupBtn.className = 'step-indicator-nav';210        nextGroupBtn.innerHTML = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>';211        nextGroupBtn.setAttribute('aria-label', 'Next group');212        nextGroupBtn.addEventListener('click', () => onClick(groupEnd + 1));213        container.appendChild(nextGroupBtn);214    }215}216 217// Update UI218function updateUI() {219    const step = assemblySteps[currentStep - 1];220    const imageSrc = getStepImage(step.id);221    222    // Update step counter223    stepCounterText.textContent = `Step ${step.id}/${TOTAL_STEPS}`;224    225    // Update image226    if (imageSrc) {227        stepImage.src = imageSrc;228        stepImage.alt = `Assembly step ${step.id}`;229        stepImage.classList.remove('hidden');230        placeholder.classList.add('hidden');231    } else {232        stepImage.classList.add('hidden');233        placeholder.classList.remove('hidden');234        placeholderNumber.textContent = step.id;235    }236    237    // Update YouTube embed238    updateYouTubeEmbed(step.timestampSeconds);239    240    // Update buttons241    prevBtn.disabled = currentStep === 1;242    nextBtn.disabled = currentStep === TOTAL_STEPS;243    244    // Update step indicators245    renderStepIndicators('step-indicators', currentStep, goToStep);246    247    // Update progress bar248    const progress = (currentStep / TOTAL_STEPS) * 100;249    progressBar.style.width = `${progress}%`;250    251    // Update fullscreen UI252    updateFullscreenUI();253}254 255// Update fullscreen UI256function updateFullscreenUI() {257    const step = assemblySteps[currentStep - 1];258    const imageSrc = getStepImage(step.id);259    260    fullscreenStepTitle.textContent = `Step ${step.id}/${TOTAL_STEPS} - ${step.title}`;261    262    if (imageSrc) {263        fullscreenImage.src = imageSrc;264        fullscreenImage.alt = `Step ${step.id}`;265        fullscreenImage.classList.remove('hidden');266        fullscreenPlaceholder.classList.add('hidden');267    } else {268        fullscreenImage.classList.add('hidden');269        fullscreenPlaceholder.classList.remove('hidden');270        fullscreenPlaceholderNumber.textContent = step.id;271    }272    273    fullscreenPrevBtn.disabled = currentStep === 1;274    fullscreenNextBtn.disabled = currentStep === TOTAL_STEPS;275    276    renderStepIndicators('fullscreen-step-indicators', currentStep, goToStepFullscreen);277    278    const progress = (currentStep / TOTAL_STEPS) * 100;279    fullscreenProgressBar.style.width = `${progress}%`;280    281    updateZoomDisplay();282}283 284// Navigation functions285function goToPrevious() {286    if (currentStep > 1) {287        currentStep--;288        updateUI();289    }290}291 292function goToNext() {293    if (currentStep < TOTAL_STEPS) {294        currentStep++;295        updateUI();296    }297}298 299function goToStep(step) {300    if (step >= 1 && step <= TOTAL_STEPS) {301        currentStep = step;302        updateUI();303    }304}305 306function goToStepFullscreen(step) {307    resetZoom();308    goToStep(step);309}310 311function goToPreviousFullscreen() {312    resetZoom();313    goToPrevious();314}315 316function goToNextFullscreen() {317    resetZoom();318    goToNext();319}320 321// Fullscreen functions322function openFullscreen() {323    isFullscreen = true;324    fullscreenModal.classList.remove('hidden');325    document.body.style.overflow = 'hidden';326    updateFullscreenUI();327}328 329function closeFullscreen() {330    isFullscreen = false;331    fullscreenModal.classList.add('hidden');332    document.body.style.overflow = '';333    resetZoom();334}335 336// Zoom functions337function zoomIn() {338    scale = Math.min(scale + 0.5, 4);339    updateZoomDisplay();340}341 342function zoomOut() {343    scale = Math.max(scale - 0.5, 0.5);344    updateZoomDisplay();345}346 347function resetZoom() {348    scale = 1;349    position = { x: 0, y: 0 };350    updateZoomDisplay();351}352 353function updateZoomDisplay() {354    zoomLevel.textContent = `${Math.round(scale * 100)}%`;355    fullscreenImage.style.transform = `translate(${position.x}px, ${position.y}px) scale(${scale})`;356}357 358// Drag functions for fullscreen image359function handleMouseDown(e) {360    if (scale > 1) {361        isDragging = true;362        dragStart = {363            x: e.clientX - position.x,364            y: e.clientY - position.y365        };366        fullscreenImageContainer.style.cursor = 'grabbing';367    }368}369 370function handleMouseMove(e) {371    if (isDragging && scale > 1) {372        position = {373            x: e.clientX - dragStart.x,374            y: e.clientY - dragStart.y375        };376        updateZoomDisplay();377    }378}379 380function handleMouseUp() {381    isDragging = false;382    fullscreenImageContainer.style.cursor = 'grab';383}384 385function handleWheel(e) {386    e.preventDefault();387    const delta = e.deltaY > 0 ? -0.2 : 0.2;388    scale = Math.max(0.5, Math.min(4, scale + delta));389    updateZoomDisplay();390}391 392// Event Listeners393prevBtn.addEventListener('click', goToPrevious);394nextBtn.addEventListener('click', goToNext);395fullscreenBtn.addEventListener('click', openFullscreen);396stepImage.addEventListener('click', openFullscreen);397closeFullscreenBtn.addEventListener('click', closeFullscreen);398fullscreenPrevBtn.addEventListener('click', goToPreviousFullscreen);399fullscreenNextBtn.addEventListener('click', goToNextFullscreen);400zoomInBtn.addEventListener('click', zoomIn);401zoomOutBtn.addEventListener('click', zoomOut);402 403fullscreenImageContainer.addEventListener('mousedown', handleMouseDown);404fullscreenImageContainer.addEventListener('mousemove', handleMouseMove);405fullscreenImageContainer.addEventListener('mouseup', handleMouseUp);406fullscreenImageContainer.addEventListener('mouseleave', handleMouseUp);407fullscreenImageContainer.addEventListener('wheel', handleWheel, { passive: false });408 409// Keyboard navigation410document.addEventListener('keydown', (e) => {411    if (isFullscreen) {412        if (e.key === 'Escape') closeFullscreen();413        if (e.key === '+' || e.key === '=') zoomIn();414        if (e.key === '-') zoomOut();415        if (e.key === '0') resetZoom();416        if (e.key === 'ArrowLeft' && currentStep > 1) goToPreviousFullscreen();417        if (e.key === 'ArrowRight' && currentStep < TOTAL_STEPS) goToNextFullscreen();418    } else {419        if (e.key === 'ArrowRight') goToNext();420        if (e.key === 'ArrowLeft') goToPrevious();421    }422});423 424// Initialize425updateUI();426