// Assembly Steps Data const YOUTUBE_VIDEO_ID = "WeKKdnuXca4"; const stepsData = [ { timestamp: "00:14", title: "Set all parts aside" }, { timestamp: "00:45", title: "Stick Foot Pads" }, { timestamp: "01:30", title: "Position the protective sleeve on the USB cable" }, { timestamp: "02:51", title: "Fix the USB Extension Cable" }, { timestamp: "03:23", title: "Insert the Power Board" }, { timestamp: "03:39", title: "Fix the Power Board" }, { timestamp: "04:30", title: "Connect the Battery" }, { timestamp: "05:23", title: "Connect the cables to the Power Board" }, { timestamp: "06:27", title: "Position the Bottom Assembly" }, { timestamp: "07:34", title: "Fix the Foot Assembly to the Bottom Assembly" }, { timestamp: "08:31", title: "Connect the Foot Motor" }, { timestamp: "09:15", title: "Screw the link rods onto the motor arms" }, { timestamp: "12:57", title: "Connect Motor 1 to 2" }, { timestamp: "13:23", title: "Connect Motor 2 to 3" }, { timestamp: "13:45", title: "Connect Motor 4 to 5" }, { timestamp: "14:05", title: "Connect Motor 5 to 6" }, { timestamp: "14:31", title: "Insert all motors into the Bottom Assembly" }, { timestamp: "16:13", title: "Clip the motor cables into the Bottom Assembly" }, { timestamp: "18:56", title: "Position the Tricap" }, { timestamp: "19:37", title: "Route the cables" }, { timestamp: "20:23", title: "Connect Motor 3 to 4" }, { timestamp: "21:06", title: "Route the cables" }, { timestamp: "23:28", title: "Check rotation" }, { timestamp: "23:53", title: "Screw the Tricap" }, { timestamp: "25:44", title: "Screw the Bottom Head onto the link rods" }, { timestamp: "28:58", title: "Route the cables through the Bottom Head" }, { timestamp: "30:07", title: "Route the Wi-Fi Antenna and the fan cable through the Head PCB" }, { timestamp: "30:14", title: "Connect the flexible camera cable (step for reference only)" }, { timestamp: "30:47", title: "Route the cables through the Head PCB" }, { timestamp: "31:48", title: "Screw the Head PCB" }, { timestamp: "33:00", title: "Position the Top Shell" }, { timestamp: "34:00", title: "Screw the Top Shell" }, { timestamp: "35:32", title: "Place the lenses in the Glasses Holder" }, { timestamp: "35:43", title: "Insert the Fisheye Lenses into the caps" }, { timestamp: "36:07", title: "Snap the Fisheye Lenses" }, { timestamp: "36:52", title: "Screw the Glasses Assembly onto the Front Head Shell" }, { timestamp: "37:53", title: "Attach the cases to Antenna's motors" }, { timestamp: "38:24", title: "Fix Antenna's Assembly to Back Head Shell" }, { timestamp: "39:41", title: "Connect the Antenna's motors" }, { timestamp: "40:22", title: "Slide the Back Head Assembly onto the Reachy Mini body" }, { timestamp: "40:45", title: "Screw the Back Head" }, { timestamp: "41:45", title: "Fix the Cable Holder" }, { timestamp: "42:20", title: "Connect Speaker, fan and Motor Cables" }, { timestamp: "42:53", title: "Connect Power and USB Extension Cables" }, { timestamp: "43:28", title: "Stick the Wi-Fi Antenna" }, { timestamp: "44:08", title: "Slide the Top Head Assembly onto the Back Head" }, { timestamp: "44:33", title: "Connect the Flexible Printed Cable to the Head PCB" }, { timestamp: "44:52", title: "Connect the Flexible Camera Cable on the Front Head" }, { timestamp: "45:16", title: "Fix the Front Head" }, { timestamp: "46:25", title: "Fix the Antennas" }, { timestamp: "47:08", title: "Turn on Reachy Mini" }, ]; // Parse timestamp to seconds (MM:SS format) function parseTimestamp(ts) { const parts = ts.split(':').map(Number); return parts[0] * 60 + parts[1]; } // Create assembly steps with parsed timestamps const assemblySteps = stepsData.map((step, index) => ({ id: index + 1, title: step.title, timestamp: step.timestamp, timestampSeconds: parseTimestamp(step.timestamp) })); const TOTAL_STEPS = assemblySteps.length; // Available step images const availableImages = { 1: 'assets/step1.jpg', 2: 'assets/step2.jpg', 3: 'assets/step3.jpg', 4: 'assets/step4.jpg', 5: 'assets/step5.jpg', 6: 'assets/step6.jpg', 7: 'assets/step7.jpg', 8: 'assets/step8.jpg', 9: 'assets/step9.jpg', 10: 'assets/step10.jpg', 11: 'assets/step11.jpg', 12: 'assets/step12.jpg', 13: 'assets/step13.jpg', 14: 'assets/step14.jpg', 15: 'assets/step15.jpg', 16: 'assets/step16.jpg', 17: 'assets/step17.jpg', 18: 'assets/step18.jpg', 19: 'assets/step19.jpg', 20: 'assets/step20.jpg', 21: 'assets/step21.jpg', 22: 'assets/step22.jpg', 23: 'assets/step23.jpg', 24: 'assets/step24.jpg', 25: 'assets/step25.jpg', 26: 'assets/step26.jpg', 27: 'assets/step27.jpg', 28: 'assets/step28.jpg', 29: 'assets/step29.jpg', 30: 'assets/step30.jpg', 31: 'assets/step31.jpg', 32: 'assets/step32.jpg', 33: 'assets/step33.jpg', 34: 'assets/step34.jpg', 35: 'assets/step35.jpg', 36: 'assets/step36.jpg', 37: 'assets/step37.jpg', 38: 'assets/step38.jpg', 39: 'assets/step39.jpg', 40: 'assets/step40.jpg', 41: 'assets/step41.jpg', 42: 'assets/step42.jpg', 43: 'assets/step43.jpg', 44: 'assets/step44.jpg', 45: 'assets/step45.jpg', 46: 'assets/step46.jpg', 47: 'assets/step47.jpg', 48: 'assets/step48.jpg', 49: 'assets/step49.jpg', 50: 'assets/step50.jpg', 51: 'assets/step51.jpg', }; function getStepImage(stepId) { return availableImages[stepId] || null; } // App State let currentStep = 1; let isFullscreen = false; let scale = 1; let position = { x: 0, y: 0 }; let isDragging = false; let dragStart = { x: 0, y: 0 }; // DOM Elements const stepCounterText = document.getElementById('step-counter-text'); const stepImage = document.getElementById('step-image'); const placeholder = document.getElementById('placeholder'); const placeholderNumber = document.getElementById('placeholder-number'); const imageWrapper = document.getElementById('image-wrapper'); const youtubeIframeDesktop = document.getElementById('youtube-iframe-desktop'); const youtubeIframeMobile = document.getElementById('youtube-iframe-mobile'); const prevBtn = document.getElementById('prev-btn'); const nextBtn = document.getElementById('next-btn'); const stepIndicators = document.getElementById('step-indicators'); const progressBar = document.getElementById('progress-bar'); const fullscreenBtn = document.getElementById('fullscreen-btn'); const fullscreenModal = document.getElementById('fullscreen-modal'); const closeFullscreenBtn = document.getElementById('close-fullscreen-btn'); const fullscreenStepTitle = document.getElementById('fullscreen-step-title'); const fullscreenImage = document.getElementById('fullscreen-image'); const fullscreenPlaceholder = document.getElementById('fullscreen-placeholder'); const fullscreenPlaceholderNumber = document.getElementById('fullscreen-placeholder-number'); const fullscreenImageContainer = document.getElementById('fullscreen-image-container'); const fullscreenYoutubeIframeDesktop = document.getElementById('fullscreen-youtube-iframe-desktop'); const fullscreenYoutubeIframeMobile = document.getElementById('fullscreen-youtube-iframe-mobile'); const fullscreenPrevBtn = document.getElementById('fullscreen-prev-btn'); const fullscreenNextBtn = document.getElementById('fullscreen-next-btn'); const fullscreenStepIndicators = document.getElementById('fullscreen-step-indicators'); const fullscreenProgressBar = document.getElementById('fullscreen-progress-bar'); const zoomInBtn = document.getElementById('zoom-in-btn'); const zoomOutBtn = document.getElementById('zoom-out-btn'); const zoomLevel = document.getElementById('zoom-level'); // Update YouTube embed function updateYouTubeEmbed(timestampSeconds) { const embedUrl = `https://www.youtube.com/embed/${YOUTUBE_VIDEO_ID}?start=${timestampSeconds}&rel=0&autoplay=1&mute=1`; youtubeIframeDesktop.src = embedUrl; youtubeIframeMobile.src = embedUrl; fullscreenYoutubeIframeDesktop.src = embedUrl; fullscreenYoutubeIframeMobile.src = embedUrl; } // Render step indicators with pagination function renderStepIndicators(containerId, currentStep, onClick) { const container = document.getElementById(containerId); container.innerHTML = ''; const groupStart = Math.floor((currentStep - 1) / 10) * 10 + 1; const groupEnd = Math.min(groupStart + 9, TOTAL_STEPS); const canGoPrevGroup = groupStart > 1; const canGoNextGroup = groupEnd < TOTAL_STEPS; // Previous group button if (canGoPrevGroup) { const prevGroupBtn = document.createElement('button'); prevGroupBtn.className = 'step-indicator-nav'; prevGroupBtn.innerHTML = ''; prevGroupBtn.setAttribute('aria-label', 'Previous group'); prevGroupBtn.addEventListener('click', () => onClick(groupStart - 1)); container.appendChild(prevGroupBtn); } // Step number buttons for (let i = 0; i < 10; i++) { const stepNum = groupStart + i; if (stepNum > TOTAL_STEPS) break; const isActive = stepNum === currentStep; const button = document.createElement('button'); button.className = `step-indicator-num ${isActive ? 'step-indicator-num-active' : ''}`; button.textContent = stepNum; button.setAttribute('aria-label', `Go to step ${stepNum}`); button.addEventListener('click', () => onClick(stepNum)); container.appendChild(button); } // Next group button if (canGoNextGroup) { const nextGroupBtn = document.createElement('button'); nextGroupBtn.className = 'step-indicator-nav'; nextGroupBtn.innerHTML = ''; nextGroupBtn.setAttribute('aria-label', 'Next group'); nextGroupBtn.addEventListener('click', () => onClick(groupEnd + 1)); container.appendChild(nextGroupBtn); } } // Update UI function updateUI() { const step = assemblySteps[currentStep - 1]; const imageSrc = getStepImage(step.id); // Update step counter stepCounterText.textContent = `Step ${step.id}/${TOTAL_STEPS}`; // Update image if (imageSrc) { stepImage.src = imageSrc; stepImage.alt = `Assembly step ${step.id}`; stepImage.classList.remove('hidden'); placeholder.classList.add('hidden'); } else { stepImage.classList.add('hidden'); placeholder.classList.remove('hidden'); placeholderNumber.textContent = step.id; } // Update YouTube embed updateYouTubeEmbed(step.timestampSeconds); // Update buttons prevBtn.disabled = currentStep === 1; nextBtn.disabled = currentStep === TOTAL_STEPS; // Update step indicators renderStepIndicators('step-indicators', currentStep, goToStep); // Update progress bar const progress = (currentStep / TOTAL_STEPS) * 100; progressBar.style.width = `${progress}%`; // Update fullscreen UI updateFullscreenUI(); } // Update fullscreen UI function updateFullscreenUI() { const step = assemblySteps[currentStep - 1]; const imageSrc = getStepImage(step.id); fullscreenStepTitle.textContent = `Step ${step.id}/${TOTAL_STEPS} - ${step.title}`; if (imageSrc) { fullscreenImage.src = imageSrc; fullscreenImage.alt = `Step ${step.id}`; fullscreenImage.classList.remove('hidden'); fullscreenPlaceholder.classList.add('hidden'); } else { fullscreenImage.classList.add('hidden'); fullscreenPlaceholder.classList.remove('hidden'); fullscreenPlaceholderNumber.textContent = step.id; } fullscreenPrevBtn.disabled = currentStep === 1; fullscreenNextBtn.disabled = currentStep === TOTAL_STEPS; renderStepIndicators('fullscreen-step-indicators', currentStep, goToStepFullscreen); const progress = (currentStep / TOTAL_STEPS) * 100; fullscreenProgressBar.style.width = `${progress}%`; updateZoomDisplay(); } // Navigation functions function goToPrevious() { if (currentStep > 1) { currentStep--; updateUI(); } } function goToNext() { if (currentStep < TOTAL_STEPS) { currentStep++; updateUI(); } } function goToStep(step) { if (step >= 1 && step <= TOTAL_STEPS) { currentStep = step; updateUI(); } } function goToStepFullscreen(step) { resetZoom(); goToStep(step); } function goToPreviousFullscreen() { resetZoom(); goToPrevious(); } function goToNextFullscreen() { resetZoom(); goToNext(); } // Fullscreen functions function openFullscreen() { isFullscreen = true; fullscreenModal.classList.remove('hidden'); document.body.style.overflow = 'hidden'; updateFullscreenUI(); } function closeFullscreen() { isFullscreen = false; fullscreenModal.classList.add('hidden'); document.body.style.overflow = ''; resetZoom(); } // Zoom functions function zoomIn() { scale = Math.min(scale + 0.5, 4); updateZoomDisplay(); } function zoomOut() { scale = Math.max(scale - 0.5, 0.5); updateZoomDisplay(); } function resetZoom() { scale = 1; position = { x: 0, y: 0 }; updateZoomDisplay(); } function updateZoomDisplay() { zoomLevel.textContent = `${Math.round(scale * 100)}%`; fullscreenImage.style.transform = `translate(${position.x}px, ${position.y}px) scale(${scale})`; } // Drag functions for fullscreen image function handleMouseDown(e) { if (scale > 1) { isDragging = true; dragStart = { x: e.clientX - position.x, y: e.clientY - position.y }; fullscreenImageContainer.style.cursor = 'grabbing'; } } function handleMouseMove(e) { if (isDragging && scale > 1) { position = { x: e.clientX - dragStart.x, y: e.clientY - dragStart.y }; updateZoomDisplay(); } } function handleMouseUp() { isDragging = false; fullscreenImageContainer.style.cursor = 'grab'; } function handleWheel(e) { e.preventDefault(); const delta = e.deltaY > 0 ? -0.2 : 0.2; scale = Math.max(0.5, Math.min(4, scale + delta)); updateZoomDisplay(); } // Event Listeners prevBtn.addEventListener('click', goToPrevious); nextBtn.addEventListener('click', goToNext); fullscreenBtn.addEventListener('click', openFullscreen); stepImage.addEventListener('click', openFullscreen); closeFullscreenBtn.addEventListener('click', closeFullscreen); fullscreenPrevBtn.addEventListener('click', goToPreviousFullscreen); fullscreenNextBtn.addEventListener('click', goToNextFullscreen); zoomInBtn.addEventListener('click', zoomIn); zoomOutBtn.addEventListener('click', zoomOut); fullscreenImageContainer.addEventListener('mousedown', handleMouseDown); fullscreenImageContainer.addEventListener('mousemove', handleMouseMove); fullscreenImageContainer.addEventListener('mouseup', handleMouseUp); fullscreenImageContainer.addEventListener('mouseleave', handleMouseUp); fullscreenImageContainer.addEventListener('wheel', handleWheel, { passive: false }); // Keyboard navigation document.addEventListener('keydown', (e) => { if (isFullscreen) { if (e.key === 'Escape') closeFullscreen(); if (e.key === '+' || e.key === '=') zoomIn(); if (e.key === '-') zoomOut(); if (e.key === '0') resetZoom(); if (e.key === 'ArrowLeft' && currentStep > 1) goToPreviousFullscreen(); if (e.key === 'ArrowRight' && currentStep < TOTAL_STEPS) goToNextFullscreen(); } else { if (e.key === 'ArrowRight') goToNext(); if (e.key === 'ArrowLeft') goToPrevious(); } }); // Initialize updateUI();