bf 6
This commit is contained in:
+12
-355
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user