This commit is contained in:
juanatsap
2025-11-12 22:54:46 +00:00
parent f48ae9388e
commit c99bb5590b
5 changed files with 421 additions and 385 deletions
+12 -355
View File
@@ -64,352 +64,19 @@
// =============================================================================
// =============================================================================
// ZOOM CONTROL
// ZOOM CONTROL - Now handled by Hyperscript in zoom-control.html
// =============================================================================
/**
* Check if we're on mobile viewport
* @returns {boolean} True if mobile (viewport <= 768px)
*/
function isMobileView() {
return window.innerWidth <= 768;
}
/**
* Initialize zoom control on page load
* Restores saved zoom level from localStorage (desktop only)
*/
function initZoomControl() {
const slider = document.getElementById('zoom-slider');
const resetBtn = document.getElementById('zoom-reset');
const zoomWrapper = document.getElementById('zoom-wrapper');
if (!slider || !zoomWrapper) return;
// On mobile, always use 100% zoom (zoom control is hidden anyway)
if (isMobileView()) {
slider.value = 100;
applyZoom(100, false);
return; // Skip event listeners on mobile
}
// Desktop: Restore saved zoom level from localStorage
const savedZoom = localStorage.getItem('cv-zoom');
if (savedZoom) {
const zoomValue = parseInt(savedZoom, 10);
slider.value = zoomValue;
applyZoom(zoomValue, false); // false = don't save (already loaded from storage)
}
// Real-time slider updates - immediate, smooth analog experience
slider.addEventListener('input', function(e) {
const zoomValue = parseInt(e.target.value, 10);
// Apply zoom and update display immediately for smooth analog feel
updateZoomDisplay(zoomValue);
applyZoom(zoomValue, true);
});
// Reset button
if (resetBtn) {
resetBtn.addEventListener('click', function() {
slider.value = 100;
applyZoom(100, true);
slider.focus(); // Return focus to slider for accessibility
});
}
// Keyboard shortcuts (Ctrl/Cmd + Plus/Minus/0)
document.addEventListener('keydown', function(e) {
if ((e.ctrlKey || e.metaKey) && !e.shiftKey) {
if (e.key === '=' || e.key === '+') {
e.preventDefault();
incrementZoom(10);
} else if (e.key === '-') {
e.preventDefault();
incrementZoom(-10);
} else if (e.key === '0') {
e.preventDefault();
slider.value = 100;
applyZoom(100, true);
}
}
});
// Handle window resize - reset zoom when switching to mobile
let resizeTimeout;
window.addEventListener('resize', function() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(function() {
if (isMobileView()) {
// Reset to 100% zoom when switching to mobile
slider.value = 100;
applyZoom(100, false);
}
}, 250); // Debounce resize events
});
}
/**
* Apply zoom transformation to CV paper
* @param {number} zoomValue - Zoom percentage (25-175, centered at 100)
* @param {boolean} saveToStorage - Whether to persist to localStorage
*/
function applyZoom(zoomValue, saveToStorage = true) {
const zoomWrapper = document.getElementById('zoom-wrapper');
if (!zoomWrapper) return;
// Convert percentage to decimal (100 = 1.0, 50 = 0.5, etc.)
const zoomLevel = zoomValue / 100;
requestAnimationFrame(() => {
// Use CSS zoom property - it properly affects layout and extends beyond viewport
zoomWrapper.style.zoom = zoomLevel;
// When zoom > 100%, allow the wrapper to expand beyond viewport width
// Set width to accommodate the expanded content without bounds
if (zoomLevel > 1) {
// Set width to auto to allow natural expansion
zoomWrapper.style.width = 'auto';
zoomWrapper.style.minWidth = '100%';
// Remove max-width constraint to allow horizontal expansion
zoomWrapper.style.maxWidth = 'none';
} else {
// Reset to default when zoom <= 100%
zoomWrapper.style.width = '';
zoomWrapper.style.minWidth = '';
zoomWrapper.style.maxWidth = '';
}
// Reset zoom on fixed buttons so they stay same size
const backToTopBtn = document.getElementById('back-to-top');
const infoBtn = document.getElementById('info-button');
const inverseZoom = 1 / zoomLevel;
if (backToTopBtn) backToTopBtn.style.zoom = inverseZoom;
if (infoBtn) infoBtn.style.zoom = inverseZoom;
// Update display
updateZoomDisplay(zoomValue);
// Save to localStorage
if (saveToStorage) {
localStorage.setItem('cv-zoom', zoomValue.toString());
}
// Update zoom control position for horizontal scroll
updateZoomControlPosition();
});
}
/**
* Update visual display and ARIA attributes
* @param {number} zoomValue - Current zoom percentage
*/
function updateZoomDisplay(zoomValue) {
const slider = document.getElementById('zoom-slider');
const display = document.getElementById('zoom-value-current');
const resetBtn = document.getElementById('zoom-reset');
if (display) {
display.textContent = zoomValue;
}
if (slider) {
slider.setAttribute('aria-valuenow', zoomValue);
slider.setAttribute('aria-valuetext', `${zoomValue}%`);
}
// Add/remove class to enable green hover only when zoom is not 100
if (resetBtn) {
if (zoomValue !== 100) {
resetBtn.classList.add('zoom-not-default');
} else {
resetBtn.classList.remove('zoom-not-default');
}
}
}
/**
* Increment/decrement zoom by step amount
* @param {number} step - Amount to change (positive or negative)
*/
function incrementZoom(step) {
const slider = document.getElementById('zoom-slider');
if (!slider) return;
const currentZoom = parseInt(slider.value, 10);
const newZoom = Math.min(175, Math.max(25, currentZoom + step));
slider.value = newZoom;
applyZoom(newZoom, true);
}
/**
* Update zoom control position based on horizontal scroll
* This keeps the zoom control centered relative to the visible viewport
*/
function updateZoomControlPosition() {
const zoomControl = document.getElementById('zoom-control');
if (!zoomControl || isMobileView()) return;
// Only adjust if zoom control is in default centered position
// (not dragged to a custom position)
const savedPosition = localStorage.getItem('cv-zoom-position');
if (savedPosition) return; // Don't adjust if user has dragged it
// Get current horizontal scroll position
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
// Update left position to account for horizontal scroll
if (scrollLeft > 0) {
// Adjust position to stay centered in viewport during horizontal scroll
zoomControl.style.left = `calc(50% + ${scrollLeft}px)`;
} else {
// Reset to center when scroll is at start
zoomControl.style.left = '50%';
}
}
/**
* Make zoom control draggable and persist position
*/
function initZoomDragging() {
const zoomControl = document.getElementById('zoom-control');
if (!zoomControl || isMobileView()) return;
let isDragging = false;
let currentX, currentY, initialX, initialY;
// Restore saved position from localStorage
const savedPosition = localStorage.getItem('cv-zoom-position');
if (savedPosition) {
const { bottom, left } = JSON.parse(savedPosition);
zoomControl.style.bottom = bottom;
zoomControl.style.left = left;
zoomControl.style.transform = 'none'; // Remove centering transform when positioned
}
// Start drag on mousedown (but not on slider, close button, or reset button)
zoomControl.addEventListener('mousedown', function(e) {
// Ignore if clicking on interactive elements
if (e.target.closest('.zoom-slider, .zoom-close-btn, .zoom-reset-btn')) {
return;
}
isDragging = true;
zoomControl.style.transition = 'none'; // Disable transitions during drag
// Get current position
const rect = zoomControl.getBoundingClientRect();
initialX = e.clientX - rect.left;
initialY = e.clientY - rect.top;
e.preventDefault();
});
// Drag on mousemove
document.addEventListener('mousemove', function(e) {
if (!isDragging) return;
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
// Keep within viewport bounds
const maxX = window.innerWidth - zoomControl.offsetWidth;
const maxY = window.innerHeight - zoomControl.offsetHeight;
currentX = Math.max(0, Math.min(currentX, maxX));
currentY = Math.max(0, Math.min(currentY, maxY));
// Update position
zoomControl.style.left = currentX + 'px';
zoomControl.style.bottom = (window.innerHeight - currentY - zoomControl.offsetHeight) + 'px';
zoomControl.style.transform = 'none'; // Remove centering transform
});
// End drag on mouseup
document.addEventListener('mouseup', function() {
if (isDragging) {
isDragging = false;
zoomControl.style.transition = 'all 0.3s ease'; // Re-enable transitions
// Save position to localStorage
const position = {
bottom: zoomControl.style.bottom,
left: zoomControl.style.left
};
localStorage.setItem('cv-zoom-position', JSON.stringify(position));
}
});
}
/**
* Hide zoom control and show menu button
*/
function hideZoomControl() {
const zoomControl = document.getElementById('zoom-control');
const showButton = document.getElementById('show-zoom-menu-btn');
if (zoomControl) {
zoomControl.style.display = 'none';
localStorage.setItem('cv-zoom-visible', 'false');
}
if (showButton) {
showButton.style.display = 'block';
}
}
/**
* Show zoom control and hide menu button (global function for onclick)
*/
window.showZoomControl = function(event) {
if (event) event.preventDefault(); // Prevent default link behavior
const zoomControl = document.getElementById('zoom-control');
const showButton = document.getElementById('show-zoom-menu-btn');
if (zoomControl) {
zoomControl.style.display = 'flex';
localStorage.setItem('cv-zoom-visible', 'true');
}
if (showButton) {
showButton.style.display = 'none';
}
};
/**
* Initialize zoom visibility state from localStorage
*/
function initZoomVisibility() {
if (isMobileView()) return; // Always hidden on mobile
const zoomControl = document.getElementById('zoom-control');
const showButton = document.getElementById('show-zoom-menu-btn');
const isVisible = localStorage.getItem('cv-zoom-visible');
// Default to visible if not set
if (isVisible === 'false') {
if (zoomControl) zoomControl.style.display = 'none';
if (showButton) showButton.style.display = 'block';
} else {
if (zoomControl) zoomControl.style.display = 'flex';
if (showButton) showButton.style.display = 'none';
}
// Setup close button
const closeBtn = document.getElementById('zoom-close');
if (closeBtn) {
closeBtn.addEventListener('click', function(e) {
e.stopPropagation(); // Prevent drag from starting
hideZoomControl();
});
}
}
// All zoom functionality moved to declarative hyperscript:
// - Slider updates and real-time zoom application
// - Reset button (back to 100%)
// - Close/show toggle with localStorage persistence
// - Keyboard shortcuts (Ctrl/Cmd +/-/0)
// - Draggable positioning with bounds checking
// - Mobile detection and auto-disable
// - LocalStorage persistence (zoom level, visibility, position)
//
// Result: ~343 lines of JavaScript eliminated!
// =============================================================================
// PRINT & PDF
@@ -482,14 +149,7 @@
localStorage.setItem('cv-language', urlLang);
}
// Initialize zoom control (zoom level, event listeners)
initZoomControl();
// Initialize zoom visibility state (show/hide based on localStorage)
initZoomVisibility();
// Initialize zoom dragging (make draggable, restore position)
initZoomDragging();
// Zoom control initialization now handled by hyperscript in zoom-control.html
}
// =============================================================================
@@ -508,9 +168,6 @@
const currentScroll = window.pageYOffset || document.documentElement.scrollTop;
const isMenuOpen = navMenu.classList.contains('menu-open');
// Update zoom control position on horizontal scroll
updateZoomControlPosition();
// Check if at bottom of page (within 50px threshold)
const scrollHeight = document.documentElement.scrollHeight;
const clientHeight = document.documentElement.clientHeight;