/* slider container — modern Windows 11 style track */ .slider-container background: rgba(10, 14, 23, 0.7); border-radius: 100px; padding: 0.45rem; box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.5), 0 1px 0 rgba(255,255,255,0.05); margin: 1rem 0 0.8rem; transition: all 0.1s;
// dimensions & state let isDragging = false; let startX = 0; let currentTranslateX = 0; // current thumb offset (px) let maxOffset = 0; // max allowed offset (px) let trackRect = null; let animationFrame = null; let shutdownTriggered = false; // once shutdown is triggered, interaction locked slide to shutdown windows 11
// attach event listeners function initEvents() // pointer + touch events thumb.addEventListener('pointerdown', onPointerDown); window.addEventListener('pointermove', onPointerMove); window.addEventListener('pointerup', onPointerUp); // touch fallback: ensure touchmove prevent default thumb.addEventListener('touchstart', onPointerDown, passive: false); window.addEventListener('touchmove', onPointerMove, passive: false); window.addEventListener('touchend', onPointerUp); window.addEventListener('resize', () => setTimeout(handleResize, 20); ); // also track container may change on font load, use ResizeObserver const resizeObserver = new ResizeObserver(() => handleResize()); if(trackContainer) resizeObserver.observe(trackContainer); handleResize(); /* slider container — modern Windows 11 style track */
/* the filled part (progress behind thumb) */ .slider-fill position: absolute; top: 0; left: 0; height: 100%; background: linear-gradient(90deg, #3b7cff, #5a9eff); border-radius: 100px; width: 0%; transition: width 0.05s linear; pointer-events: none; box-shadow: inset 0 1px 1px rgba(255,255,255,0.3); const trackRectCached = trackContainer
function onPointerUp(e) if (!isDragging) return; isDragging = false; document.body.style.cursor = ''; if (shutdownTriggered) // if shutdown triggered, don't snap back if(thumb) thumb.style.transform = `translateX($maxOffsetpx)`; return; // snap back to 0 if not fully completed (except if exactly at max) if (currentTranslateX < maxOffset - 1) // smooth snap to zero (with animation transition) thumb.style.transition = 'transform 0.2s cubic-bezier(0.2, 0.9, 0.4, 1.1)'; setThumbOffset(0, true); // reset transition after animation setTimeout(() => if(thumb) thumb.style.transition = ''; , 220); // update status hint statusDiv.innerHTML = `<span>↺ Slid back — try again to shutdown</span>`; setTimeout(() => if(!shutdownTriggered && statusDiv.innerHTML.includes('↺ Slid back')) statusDiv.innerHTML = `<span>🔘 Drag the circle to the end ➔</span>`; , 1500); else if (currentTranslateX >= maxOffset - 0.5 && !shutdownTriggered) // ensure shutdown is triggered if at the edge performShutdown(); // release pointer capture if (thumb) thumb.releasePointerCapture?.(e.pointerId);
function onPointerMove(e) if (!isDragging) return; if (shutdownTriggered) isDragging = false; return; e.preventDefault(); let clientX = e.clientX; if (e.touches) clientX = e.touches[0].clientX; e.preventDefault(); // get current track boundaries relative to dragZone if (!trackContainer) return; const trackBounds = trackContainer.getBoundingClientRect(); const thumbWidth = thumb.offsetWidth; // compute new left position: pointer position - start offset let newThumbLeft = clientX - startX; // clamp within track container bounds (left bound and right bound) const minLeft = trackBounds.left; const maxLeft = trackBounds.right - thumbWidth; let clampedLeft = Math.min(maxLeft, Math.max(minLeft, newThumbLeft)); // compute translateX = clampedLeft - originalLeft (original thumb position relative to track) // easier: get current transform? we compute relative offset from initial position. // but we want offset relative to left start = 0px. const trackRectCached = trackContainer.getBoundingClientRect(); const offsetFromTrackStart = clampedLeft - trackRectCached.left; let translateValue = Math.min(maxOffset, Math.max(0, offsetFromTrackStart)); // apply to thumb if(thumb) thumb.style.transform = `translateX($translateValuepx)`; currentTranslateX = translateValue; // update fill width based on progress updateFillAndLabel(translateValue); // if fully slid, performShutdown will be called inside setThumbOffset logic ( but we call manually also ) if (!shutdownTriggered && maxOffset > 0 && translateValue >= maxOffset - 0.2) // ensure full engagement if(translateValue >= maxOffset - 0.01) performShutdown(); else // snap to max if close enough? if(translateValue > maxOffset - 1 && translateValue < maxOffset) setThumbOffset(maxOffset, true);