# Zoom Implementation - Technical Documentation ## Overview This document describes the technical implementation of the custom zoom feature in the CV application, including architectural decisions, constraints, and solutions to specific challenges. ## Implementation Summary - **Range**: 25% - 175% (centered at 100%) - **Technology**: CSS `zoom` property on wrapper element - **Fixed Elements**: Action bar, zoom control, info button, back-to-top button - **Viewport Behavior**: Horizontal scroll enabled when zoomed > 100% ## Technical Architecture ### 1. Core Zoom Application **Location**: `static/js/main.js` - `applyZoom()` function ```javascript function applyZoom(zoomValue, saveToStorage = true) { const zoomWrapper = document.getElementById('zoom-wrapper'); const zoomLevel = zoomValue / 100; // Apply CSS zoom to wrapper zoomWrapper.style.zoom = zoomLevel; // Allow horizontal expansion when zoomed > 100% if (zoomLevel > 1) { zoomWrapper.style.minWidth = `${window.innerWidth}px`; } else { zoomWrapper.style.minWidth = ''; } // Apply inverse zoom to fixed buttons const inverseZoom = 1 / zoomLevel; document.getElementById('back-to-top').style.zoom = inverseZoom; document.getElementById('info-button').style.zoom = inverseZoom; } ``` ### 2. HTML Structure **Location**: `templates/index.html` ```html
...
...
``` ## Critical Design Decisions ### Decision 1: CSS `zoom` vs `transform: scale()` **Choice**: CSS `zoom` property **Why**: - **Layout impact**: `zoom` affects actual layout dimensions, making scrollbars appear naturally - **Footer positioning**: With `zoom`, footer automatically follows zoomed content - **Browser behavior**: Mimics native browser zoom behavior more closely **Why NOT `transform: scale()`**: - Only visual transformation, doesn't affect layout - Creates infinite gray space below content - Requires complex margin calculations to position footer - Doesn't trigger natural scrollbar behavior ### Decision 2: Inverse Zoom for Fixed Elements **Problem**: CSS `zoom` on parent can affect fixed-position children in some rendering contexts **Solution**: Apply inverse zoom to elements that must stay constant size ```javascript const inverseZoom = 1 / zoomLevel; button.style.zoom = inverseZoom; ``` **Example**: - Content zoom: 150% (1.5) - Button inverse zoom: 66.67% (1/1.5) - Result: Button appears at 100% (1.5 × 0.667 = 1.0) **Affected Elements**: - `#back-to-top` button - `#info-button` button - Note: `#zoom-control` and `.action-bar` are naturally outside zoom context ### Decision 3: Viewport Expansion Strategy **Problem**: Elements with `width: 100%` stay constrained to viewport even when zoomed **Solution**: Set `min-width` on zoom wrapper when zoom > 100% ```javascript if (zoomLevel > 1) { zoomWrapper.style.minWidth = `${window.innerWidth}px`; } ``` **Effect**: - Content can expand beyond viewport width - Horizontal scrollbar appears automatically - User can scroll to see all zoomed content **CSS Support**: ```css body { overflow-x: auto; /* Enable horizontal scroll */ } ``` ## Constraints and Limitations ### 1. Browser Compatibility **CSS `zoom` property**: - ✅ Chrome, Edge, Safari: Full support - ⚠️ Firefox: Supported since Firefox 126 (May 2024) - Fallback: Content remains at 100% zoom in unsupported browsers ### 2. Range Limitations **Current Range**: 25% - 175% **Why centered at 100%**: - Slider midpoint = (25 + 175) / 2 = 100 - Users expect "reset" to be at slider center - Provides balanced zoom-in/zoom-out range **Why NOT 50% - 200%**: - 50% minimum would make slider center = 125%, confusing UX - Current range provides adequate zoom range while maintaining intuitive controls ### 3. Fixed Element Positioning **Elements that MUST stay outside zoom-wrapper**: - Action bar (navigation) - Zoom control - Fixed buttons (info, back-to-top) - Footer **If placed inside zoom-wrapper**: - Would scale with content - Requires inverse zoom application (added complexity) - Footer positioning becomes unreliable ### 4. Performance Considerations **CSS `zoom` Performance**: - Generally performant (GPU-accelerated in modern browsers) - Can cause reflow when changed - Mitigated by `requestAnimationFrame()` wrapper **Optimization**: ```javascript requestAnimationFrame(() => { zoomWrapper.style.zoom = zoomLevel; // Other DOM modifications }); ``` ### 5. Mobile Behavior **Current Implementation**: Zoom control hidden on mobile (≤768px viewport) **Rationale**: - Mobile browsers provide native pinch-to-zoom - Custom zoom control redundant on touch devices - Saves screen space on small viewports **Code**: ```javascript function isMobileView() { return window.innerWidth <= 768; } ``` ## Edge Cases Handled ### 1. Zoom Persistence **Behavior**: Zoom level saved to `localStorage` ```javascript localStorage.setItem('cv-zoom', zoomValue.toString()); ``` **On Page Load**: Restore saved zoom level ```javascript const savedZoom = localStorage.getItem('cv-zoom'); if (savedZoom) { applyZoom(parseInt(savedZoom, 10), false); } ``` ### 2. Responsive Resize **Problem**: User resizes window while zoomed **Solution**: Recalculate min-width on window resize ```javascript window.addEventListener('resize', function() { // Debounced resize handler if (isMobileView()) { applyZoom(100, false); // Reset on mobile } }); ``` ### 3. Reset Button Green Indicator **Feature**: Reset button turns green when zoom ≠ 100% **Implementation**: ```javascript if (zoomValue !== 100) { resetBtn.classList.add('zoom-not-default'); } else { resetBtn.classList.remove('zoom-not-default'); } ``` **CSS**: ```css .zoom-reset-btn.zoom-not-default:hover { background: #74aacd; } ``` ### 4. Keyboard Shortcuts **Supported Shortcuts**: - `Ctrl/Cmd + Plus`: Zoom in (+10%) - `Ctrl/Cmd + Minus`: Zoom out (-10%) - `Ctrl/Cmd + 0`: Reset to 100% **Implementation**: ```javascript document.addEventListener('keydown', function(e) { if ((e.ctrlKey || e.metaKey) && !e.shiftKey) { if (e.key === '=' || e.key === '+') { e.preventDefault(); incrementZoom(10); } // ... other shortcuts } }); ``` ## Bottom Scroll Detection Integration **Feature**: Info and back-to-top buttons turn green when at page bottom **Challenge**: Zoom affects scroll calculations **Solution**: Bottom detection works independently of zoom ```javascript const scrollHeight = document.documentElement.scrollHeight; const clientHeight = document.documentElement.clientHeight; const isAtBottom = (scrollHeight - currentScroll - clientHeight) < 50; ``` **Why it works**: `scrollHeight` and `clientHeight` automatically account for zoomed content dimensions ## Future Considerations ### Potential Enhancements 1. **Smooth Zoom Transitions** - Current: Instant zoom change - Possible: CSS transition on zoom property - Trade-off: May feel laggy for large zoom changes 2. **Zoom Presets** - Current: Free-range slider - Possible: Preset buttons (50%, 75%, 100%, 125%, 150%) - Trade-off: Less flexible, but faster access 3. **Per-Section Zoom** - Current: Entire document zooms - Possible: Zoom specific sections independently - Trade-off: Complex state management ### Known Limitations to Monitor 1. **Firefox < 126**: No CSS zoom support (falls back to 100%) 2. **Print Behavior**: Zoom is reset for print (intentional) 3. **High Zoom Levels**: Text rendering quality may degrade > 175% ## Testing Checklist - [ ] Zoom in/out with slider - [ ] Zoom with keyboard shortcuts - [ ] Reset button functionality - [ ] Fixed elements stay constant size - [ ] Horizontal scroll appears when needed - [ ] Bottom detection works with zoom - [ ] Green button indicators work correctly - [ ] localStorage persistence works - [ ] Mobile: Zoom control hidden - [ ] Print: Zoom resets properly ## References - CSS zoom specification: [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/CSS/zoom) - Browser compatibility: [Can I Use - CSS zoom](https://caniuse.com/css-zoom) - Related: transform scale vs zoom discussion --- **Last Updated**: 2025-11-12 **Author**: Development Team **Status**: Implemented and Stable