Spaces:
Running
Running
| document.addEventListener('DOMContentLoaded', () => { | |
| const dial = document.getElementById('dial'); | |
| const fingerStop = document.getElementById('finger-stop'); | |
| const display = document.getElementById('dialed-numbers'); | |
| const callButton = document.getElementById('call-button'); | |
| const clearButton = document.getElementById('clear-button'); | |
| const dialReturnSound = document.getElementById('dial-return-sound'); | |
| const dialClickSound = document.getElementById('dial-click-sound'); | |
| if (!dialClickSound) console.error("Audio element #dial-click-sound not found!"); | |
| if (!dialReturnSound) console.warn("Audio element #dial-return-sound not found (optional)."); | |
| const numbers = ['0', '9', '8', '7', '6', '5', '4', '3', '2', '1']; | |
| const numHoles = 10; | |
| // --- Config --- | |
| const holeAngleOffset = -55; | |
| const holeAngleSeparation = 30; | |
| const fingerStopTargetAngle = holeAngleOffset + (numHoles * holeAngleSeparation) + 1; | |
| const dialPlateRadius = dial.offsetWidth / 2; | |
| const holeRadius = dialPlateRadius * 0.72; | |
| const numberPlacementRadius = holeRadius * 0.65; // Inner radius | |
| const DIAL_RETURN_DURATION_MS = 550; | |
| const CLICK_START_DELAY_MS = 50; | |
| // --- State --- | |
| let isDragging = false; | |
| let startAngle = 0; | |
| let currentRotation = 0; | |
| let activeHole = null; | |
| let maxRotationAngle = 0; | |
| let dialedDigit = null; | |
| let holeData = {}; | |
| let clickTimer = null; | |
| // --- Generate Holes and Numbers on Dial Plate --- | |
| numbers.forEach((num, index) => { | |
| const angle = holeAngleOffset + index * holeAngleSeparation; | |
| const rad = angle * (Math.PI / 180); | |
| // --- Create Hole --- | |
| const holeX = holeRadius * Math.cos(rad); | |
| const holeY = holeRadius * Math.sin(rad); | |
| const hole = document.createElement('div'); | |
| hole.classList.add('hole'); | |
| hole.dataset.number = num; | |
| hole.style.transform = `translate(${holeX}px, ${holeY}px)`; | |
| dial.appendChild(hole); | |
| // --- Create Number --- | |
| const numRad = angle * (Math.PI / 180); | |
| const numX = numberPlacementRadius * Math.cos(numRad); // Use inner radius | |
| const numY = numberPlacementRadius * Math.sin(numRad); // Use inner radius | |
| const numElement = document.createElement('div'); | |
| numElement.classList.add('dial-plate-number'); | |
| numElement.textContent = num; | |
| // 1. Position using left/top relative to dial center | |
| numElement.style.left = `calc(50% + ${numX}px)`; | |
| numElement.style.top = `calc(50% + ${numY}px)`; | |
| // 2. Apply ONLY the centering translation. NO ROTATION. | |
| numElement.style.transform = `translate(-50%, -50%)`; // REMOVED rotate() part | |
| // The numbers will now inherit the rotation of the parent #dial div. | |
| dial.appendChild(numElement); | |
| // Store info | |
| holeData[index] = { element: hole, number: num, startAngle: angle }; | |
| // Attach Event Listeners | |
| hole.addEventListener('mousedown', startDrag); | |
| hole.addEventListener('touchstart', startDrag, { passive: false }); | |
| }); | |
| // --- Position Finger Stop (Same as before) --- | |
| const stopRad = fingerStopTargetAngle * (Math.PI / 180); | |
| const stopRadius = dialPlateRadius * 0.95; | |
| const stopX = stopRadius * Math.cos(stopRad); | |
| const stopY = stopRadius * Math.sin(stopRad); | |
| fingerStop.style.left = `calc(50% + ${stopX}px - 9px)`; | |
| fingerStop.style.top = `calc(50% + ${stopY}px - 22.5px)`; | |
| fingerStop.style.transform = `rotate(${fingerStopTargetAngle + 90}deg)`; | |
| // --- Drag Logic (Remains the same) --- | |
| function startDrag(e) { | |
| if (isDragging) return; | |
| if (clickTimer) clearInterval(clickTimer); | |
| const evt = e.touches ? e.touches[0] : e; | |
| activeHole = evt.target.closest('.hole'); | |
| if (!activeHole) return; | |
| const holeInfo = Object.values(holeData).find(data => data.element === activeHole); | |
| if (!holeInfo) return; | |
| isDragging = true; | |
| dialedDigit = holeInfo.number; | |
| activeHole.classList.add('dragging'); | |
| dial.style.transition = 'none'; | |
| const rect = dial.getBoundingClientRect(); | |
| const centerX = rect.left + rect.width / 2; | |
| const centerY = rect.top + rect.height / 2; | |
| startAngle = getAngle(evt.clientX, evt.clientY, centerX, centerY) - currentRotation; | |
| maxRotationAngle = 0; | |
| document.addEventListener('mousemove', drag); | |
| document.addEventListener('mouseup', endDrag); | |
| document.addEventListener('touchmove', drag, { passive: false }); | |
| document.addEventListener('touchend', endDrag); | |
| if (e.touches) e.preventDefault(); | |
| } | |
| function drag(e) { | |
| if (!isDragging || !activeHole) return; | |
| const evt = e.touches ? e.touches[0] : e; | |
| const rect = dial.getBoundingClientRect(); | |
| const centerX = rect.left + rect.width / 2; | |
| const centerY = rect.top + rect.height / 2; | |
| let currentMouseAngle = getAngle(evt.clientX, evt.clientY, centerX, centerY); | |
| let potentialRotation = currentMouseAngle - startAngle; | |
| let delta = potentialRotation - currentRotation; | |
| if (delta > 180) delta -= 360; | |
| if (delta < -180) delta += 360; | |
| potentialRotation = currentRotation + delta; | |
| potentialRotation = Math.max(0, potentialRotation); | |
| const holeInfo = Object.values(holeData).find(data => data.element === activeHole); | |
| let holeStartAngle = holeInfo.startAngle; | |
| let maxAllowedRotation = fingerStopTargetAngle - holeStartAngle; | |
| if (maxAllowedRotation < 0) maxAllowedRotation += 360; | |
| potentialRotation = Math.min(potentialRotation, maxAllowedRotation); | |
| currentRotation = potentialRotation; | |
| maxRotationAngle = Math.max(maxRotationAngle, currentRotation); | |
| dial.style.transform = `rotate(${currentRotation}deg)`; | |
| if (e.touches) e.preventDefault(); | |
| } | |
| function endDrag() { | |
| if (!isDragging) return; | |
| document.removeEventListener('mousemove', drag); | |
| document.removeEventListener('mouseup', endDrag); | |
| document.removeEventListener('touchmove', drag); | |
| document.removeEventListener('touchend', endDrag); | |
| let numberSuccessfullyDialed = false; | |
| let digitToPlaySoundFor = null; | |
| if (activeHole) { | |
| activeHole.classList.remove('dragging'); | |
| const holeInfo = Object.values(holeData).find(data => data.element === activeHole); | |
| const rotationThreshold = 10; | |
| let holeStartAngle = holeInfo.startAngle; | |
| let expectedStopRotation = fingerStopTargetAngle - holeStartAngle; | |
| if (expectedStopRotation < 0) expectedStopRotation += 360; | |
| const tolerance = 2; | |
| if (maxRotationAngle > rotationThreshold && maxRotationAngle >= expectedStopRotation - tolerance) { | |
| appendNumber(dialedDigit); | |
| numberSuccessfullyDialed = true; | |
| digitToPlaySoundFor = dialedDigit; | |
| } | |
| } | |
| dial.style.transition = `transform ${DIAL_RETURN_DURATION_MS / 1000}s cubic-bezier(0.15, 0.85, 0.25, 1)`; | |
| dial.style.transform = 'rotate(0deg)'; | |
| if (numberSuccessfullyDialed) { | |
| playDialClickSounds(digitToPlaySoundFor); | |
| } | |
| isDragging = false; | |
| startAngle = 0; | |
| currentRotation = 0; | |
| activeHole = null; | |
| dialedDigit = null; | |
| } | |
| // --- Helper Functions (Remain the same) --- | |
| function getAngle(x, y, centerX, centerY) { | |
| const deltaX = x - centerX; | |
| const deltaY = y - centerY; | |
| let angle = Math.atan2(deltaY, deltaX) * (180 / Math.PI); | |
| return angle; | |
| } | |
| function appendNumber(num) { | |
| if (display.textContent.length < 10) { display.textContent += num; } | |
| } | |
| function clearDisplay() { | |
| display.textContent = ''; | |
| if (clickTimer) clearInterval(clickTimer); | |
| } | |
| // --- Sound Playback Logic using <audio> (Remains the same) --- | |
| function playDialClickSounds(digit) { | |
| if (!digit) return; | |
| if (clickTimer) clearInterval(clickTimer); | |
| if (dialReturnSound) { | |
| dialReturnSound.currentTime = 0; | |
| dialReturnSound.play().catch(e => {}); | |
| } | |
| if (dialClickSound) { | |
| const numClicks = (digit === '0') ? 10 : parseInt(digit); | |
| if (numClicks <= 0) return; | |
| const durationForClicks = DIAL_RETURN_DURATION_MS - CLICK_START_DELAY_MS; | |
| if (durationForClicks <= 0) { console.error("Duration for clicks too short."); return; } | |
| const clickInterval = durationForClicks / numClicks; | |
| if (clickInterval < 30) { console.warn(`Calculated click interval (${clickInterval.toFixed(1)}ms) is very fast.`); } | |
| let clickCount = 0; | |
| setTimeout(() => { | |
| if (isDragging) return; | |
| clickTimer = setInterval(() => { | |
| if (clickCount < numClicks) { | |
| const clickAudio = document.getElementById('dial-click-sound'); | |
| if(clickAudio){ | |
| clickAudio.currentTime = 0; | |
| clickAudio.play().catch(e => {}); | |
| } else { clearInterval(clickTimer); clickTimer = null; return; } | |
| clickCount++; | |
| } else { clearInterval(clickTimer); clickTimer = null; } | |
| }, Math.max(30, clickInterval)); | |
| }, CLICK_START_DELAY_MS); | |
| } else { console.error("Click sound element not found."); } | |
| } | |
| // --- Event Listeners (Remain the same) --- | |
| callButton.addEventListener('click', () => { | |
| const currentNumber = display.textContent; | |
| if (currentNumber) { alert(`Dialing ${currentNumber}...`); } | |
| }); | |
| clearButton.addEventListener('click', clearDisplay); | |
| // --- Initial Setup --- | |
| dial.style.transform = 'rotate(0deg)'; | |
| console.log("Dialer Initialized (Numbers Inner & Static Orientation)."); | |
| }); |