Anne-Charlotte's picture
Update script.js
e610d9f verified
// Assembly Steps Data
const YOUTUBE_VIDEO_ID = "PC5Yx950nMY";
const stepsData = [
{ timestamp: "00:14", title: "Set all parts aside" },
{ timestamp: "00:30", title: "Stick Foot Pads" },
{ timestamp: "01:18", title: "Fix the USB Extension Cable and add the protective sleeve" },
{ timestamp: "02:58", title: "Insert the Power Board" },
{ timestamp: "03:17", title: "Fix the Power Board" },
{ timestamp: "04:06", title: "Connect the cables to the Power Board" },
{ timestamp: "05:26", title: "Position the Bottom Assembly" },
{ timestamp: "07:20", title: "Fix the Foot Assembly to the Bottom Assembly" },
{ timestamp: "08:15", title: "Connect the Foot Motor" },
{ timestamp: "09:15", title: "Screw the link rods onto the motor arms" },
{ timestamp: "12:22", title: "Connect Motor 1 to 2" },
{ timestamp: "13:11", title: "Connect Motor 2 to 3" },
{ timestamp: "13:36", title: "Connect Motor 4 to 5" },
{ timestamp: "13:55", title: "Connect Motor 5 to 6" },
{ timestamp: "14:24", title: "Insert all motors into the Bottom Assembly" },
{ timestamp: "15:54", title: "Clip the motor's cables into the Bottom Assembly" },
{ timestamp: "19:00", title: "Position the Tricap" },
{ timestamp: "19:19", title: "Route the cables" },
{ timestamp: "19:51", title: "Connect Motor 3 to 4" },
{ timestamp: "20:40", title: "Route the cables" },
{ timestamp: "22:02", title: "Check rotation" },
{ timestamp: "22:24", title: "Screw the Tricap" },
{ timestamp: "23:56", title: "Screw the Bottom Head onto the link rods" },
{ timestamp: "27:50", title: "Route the cables through the Bottom Head" },
{ timestamp: "28:45", title: "Connect the flexible camera cable (step for reference only)" },
{ timestamp: "28:51", title: "Route the cables through the Head PCB" },
{ timestamp: "29:16", title: "Screw the Head PCB" },
{ timestamp: "30:19", title: "Position the Top Shell" },
{ timestamp: "30:53", title: "Screw the Top Shell" },
{ timestamp: "32:18", title: "Place the lenses in the Glasses Holder" },
{ timestamp: "32:30", title: "Insert the Fisheye Lenses into the caps" },
{ timestamp: "32:54", title: "Snap the Fisheye Lenses" },
{ timestamp: "33:39", title: "Screw the Glasses Assembly onto the Front Head Shell" },
{ timestamp: "34:40", title: "Attach the cases to Antenna's motors" },
{ timestamp: "34:59", title: "Fix Antenna's Assembly to Back Head Shell" },
{ timestamp: "36:30", title: "Connect the Antenna's motors" },
{ timestamp: "36:52", title: "Slide the Back Head Assembly onto the Reachy Mini body" },
{ timestamp: "37:14", title: "Screw the Back Head" },
{ timestamp: "38:00", title: "Fix the Cable Holder" },
{ timestamp: "39:45", title: "Connect Speaker and Motor Cables" },
{ timestamp: "40:07", title: "Connect Power and USB Extension Cables" },
{ timestamp: "40:25", title: "Slide the Top Head Assembly onto the Back Head" },
{ timestamp: "40:39", title: "Connect the Flexible Printed Cable to the Head PCB" },
{ timestamp: "40:50", title: "Connect the Flexible Camera Cable on the Front Head" },
{ timestamp: "41:42", title: "Fix the Front Head" },
{ timestamp: "42:56", title: "Fix the Antennas" },
];
// 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",
};
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 = '<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>';
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 = '<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>';
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();