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)."); | |
}); |