35a836adf3
Zoom level persistence was broken because hyperscript was setting the container's value instead of the slider's value on page load. Changes: - Fix zoom-control.html line 10: set #zoom-slider's value (not 'my value') - Add comprehensive zoom persistence test (10-zoom-persistence.test.mjs) - Update cv-functions.js documentation to clarify hyperscript interop - Add zoom control feature to README Test results: 5/5 tests pass - Zoom saves to localStorage when changed ✅ - Zoom restores correctly on page reload ✅ - Reset to 100% works and persists ✅ Architecture note: - Hyperscript 'call' within _="" attributes requires global JS scope - JavaScript wrappers bridge window exposure to hyperscript evaluate() - Pattern: window.fn() → _hyperscript.evaluate('hyperscriptFn()')
163 lines
6.1 KiB
HTML
163 lines
6.1 KiB
HTML
{{define "zoom-control"}}
|
|
<!-- Zoom Control (Fixed Bottom Center, Draggable) - Hyperscript Enhanced -->
|
|
<div id="zoom-control" class="zoom-control no-print zoom-hidden" role="group" aria-label="{{if eq .Lang "es"}}Control de zoom{{else}}Zoom control{{end}}"
|
|
_="on load
|
|
if window.innerWidth <= 768
|
|
exit
|
|
end
|
|
set savedZoom to localStorage.getItem('cv-zoom')
|
|
if savedZoom
|
|
set #zoom-slider's value to savedZoom
|
|
send input to #zoom-slider
|
|
end
|
|
-- Check visibility preference: show only if explicitly enabled or first visit
|
|
set isVisible to localStorage.getItem('cv-zoom-visible')
|
|
log 'Zoom control loading. cv-zoom-visible value:', isVisible
|
|
-- Show ONLY if explicitly set to 'true' (hidden by default)
|
|
if isVisible is 'true'
|
|
log 'Showing zoom control'
|
|
remove .zoom-hidden from me
|
|
add .zoom-hidden to #show-zoom-menu-btn
|
|
else
|
|
log 'Keeping zoom control hidden'
|
|
-- Already hidden via initial class, no action needed
|
|
end
|
|
set savedPos to localStorage.getItem('cv-zoom-position')
|
|
if savedPos
|
|
set pos to JSON.parse(savedPos)
|
|
set my *bottom to pos.bottom
|
|
set my *left to pos.left
|
|
set my *transform to 'none'
|
|
end
|
|
|
|
on mousedown(clientX, clientY)
|
|
-- Check if click is on interactive elements (slider, buttons)
|
|
-- IMPORTANT: Don't halt event for interactive elements so their click handlers work
|
|
set target to event.target
|
|
set targetTag to target.tagName
|
|
|
|
-- Exit if clicking on interactive elements
|
|
if targetTag is 'INPUT' exit end
|
|
if targetTag is 'BUTTON' exit end
|
|
if target.classList.contains('zoom-slider') exit end
|
|
if target.classList.contains('zoom-close-btn') exit end
|
|
if target.classList.contains('zoom-reset-btn') exit end
|
|
if target.classList.contains('zoom-value') exit end
|
|
if target.closest('.zoom-close-btn') exit end
|
|
if target.closest('.zoom-reset-btn') exit end
|
|
|
|
-- Only start dragging if clicked on the zoom control background
|
|
set :isDragging to true
|
|
set my *transition to 'none'
|
|
|
|
set rect to my getBoundingClientRect()
|
|
set :initialX to clientX - rect.left
|
|
set :initialY to clientY - rect.top
|
|
|
|
-- Prevent text selection during drag
|
|
halt the event
|
|
|
|
on mousemove(clientX, clientY) from document
|
|
if :isDragging is not true exit end
|
|
|
|
halt the event
|
|
|
|
set currentX to clientX - :initialX
|
|
set currentY to clientY - :initialY
|
|
|
|
set maxX to window.innerWidth - my offsetWidth
|
|
set maxY to window.innerHeight - my offsetHeight
|
|
|
|
set currentX to Math.max(0, Math.min(currentX, maxX))
|
|
set currentY to Math.max(0, Math.min(currentY, maxY))
|
|
|
|
set my *left to `${currentX}px`
|
|
set my *bottom to `${window.innerHeight - currentY - my offsetHeight}px`
|
|
set my *transform to 'none'
|
|
|
|
on mouseup from document
|
|
if :isDragging is not true exit end
|
|
|
|
set :isDragging to false
|
|
set my *transition to 'all 0.3s ease'
|
|
|
|
set position to { bottom: my *bottom, left: my *left }
|
|
set localStorage['cv-zoom-position'] to JSON.stringify(position)">
|
|
|
|
<button
|
|
id="zoom-close"
|
|
class="zoom-close-btn"
|
|
aria-label="{{if eq .Lang "es"}}Cerrar control de zoom{{else}}Close zoom control{{end}}"
|
|
title="{{if eq .Lang "es"}}Cerrar{{else}}Close{{end}}">
|
|
<iconify-icon icon="mdi:close" width="16" height="16" style="pointer-events: none;"></iconify-icon>
|
|
</button>
|
|
|
|
<span class="zoom-value zoom-value-min" aria-hidden="true">25</span>
|
|
|
|
<input
|
|
type="range"
|
|
id="zoom-slider"
|
|
class="zoom-slider"
|
|
min="25"
|
|
max="175"
|
|
step="1"
|
|
value="100"
|
|
aria-label="{{if eq .Lang "es"}}Ajustar nivel de zoom del CV{{else}}Adjust CV zoom level{{end}}"
|
|
aria-valuemin="25"
|
|
aria-valuemax="175"
|
|
aria-valuenow="100"
|
|
aria-valuetext="100%"
|
|
_="on input
|
|
set zoomValue to my value as a Number
|
|
set zoomLevel to zoomValue / 100
|
|
|
|
-- Update display
|
|
put zoomValue into #zoom-value-current
|
|
set my @aria-valuenow to zoomValue
|
|
set my @aria-valuetext to `${zoomValue}%`
|
|
|
|
-- Toggle reset button class
|
|
if zoomValue is not 100
|
|
add .zoom-not-default to #zoom-reset
|
|
else
|
|
remove .zoom-not-default from #zoom-reset
|
|
end
|
|
|
|
-- Apply zoom to wrapper
|
|
set #zoom-wrapper's *zoom to zoomLevel
|
|
|
|
-- Handle width for zoom > 100%
|
|
if zoomLevel > 1
|
|
set #zoom-wrapper's *width to 'auto'
|
|
set #zoom-wrapper's *minWidth to '100%'
|
|
set #zoom-wrapper's *maxWidth to 'none'
|
|
else
|
|
set #zoom-wrapper's *width to ''
|
|
set #zoom-wrapper's *minWidth to ''
|
|
set #zoom-wrapper's *maxWidth to ''
|
|
end
|
|
|
|
-- Counter-zoom fixed buttons to keep them at original size
|
|
-- These buttons are outside zoom-wrapper, so they don't need counter-zoom
|
|
-- Removing this code as it causes incorrect sizing
|
|
|
|
-- Save to localStorage
|
|
set localStorage['cv-zoom'] to zoomValue">
|
|
|
|
<span class="zoom-value zoom-value-max" aria-hidden="true">175</span>
|
|
|
|
<button
|
|
id="zoom-reset"
|
|
class="zoom-reset-btn"
|
|
aria-label="{{if eq .Lang "es"}}Restablecer zoom al 100%{{else}}Reset zoom to 100%{{end}}"
|
|
title="{{if eq .Lang "es"}}Restablecer{{else}}Reset{{end}}"
|
|
aria-live="polite"
|
|
_="on click
|
|
set #zoom-slider's value to 100
|
|
send input to #zoom-slider
|
|
send focus to #zoom-slider">
|
|
<span id="zoom-value-current">100</span>
|
|
</button>
|
|
</div>
|
|
{{end}}
|