```html
Note: Conversion is done by re-encoding via canvas, which can be slow and resource-intensive. Output quality may vary.
Note: This QR generator is basic. For complex data or high error correction, dedicated tools are recommended.
Words: 0
Characters (with spaces): 0
Characters (no spaces): 0
Spaces: 0
Reading Time (approx.): 0 minutes
HEX: #FFD700
RGB: rgb(255, 215, 0)
HSL: hsl(51, 100%, 50%)
Status: Idle
${tool.description}
`; toolGrid.appendChild(card); card.querySelector('button').addEventListener('click', () => { openTool(tool); }); }); function openTool(tool) { toolGrid.style.display = 'none'; activeToolTitle.textContent = tool.title; activeToolContent.innerHTML = ''; // Clear previous tool tool.initUI(activeToolContent); // Initialize new tool's UI activeToolContainer.style.display = 'block'; window.scrollTo(0, 0); // Scroll to top } backToToolsBtn.addEventListener('click', () => { activeToolContainer.style.display = 'none'; toolGrid.style.display = 'grid'; activeToolContent.innerHTML = ''; // Clear active tool content }); // --- Utility Functions --- function downloadDataUrl(dataUrl, filename) { const link = document.createElement('a'); link.href = dataUrl; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); } function downloadBlob(blob, filename) { const url = URL.createObjectURL(blob); downloadDataUrl(url, filename); URL.revokeObjectURL(url); } // --- Tool Modules Logic --- const ToolModules = { imageConverter: { process: () => { const fileInput = document.getElementById('imgConvFile'); const formatSelect = document.getElementById('imgConvFormat'); const outputDiv = document.getElementById('imgConvOutput'); const resultImg = document.getElementById('imgConvResult'); const downloadLink = document.getElementById('imgConvDownload'); if (!fileInput.files || fileInput.files.length === 0) { alert("Please select an image file."); return; } const file = fileInput.files[0]; const format = formatSelect.value; const filename = file.name.substring(0, file.name.lastIndexOf('.')) + '.' + format.split('/')[1]; showLoading("Converting image..."); const reader = new FileReader(); reader.onload = (e) => { const img = new Image(); img.onload = () => { const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); try { const dataUrl = canvas.toDataURL(format); resultImg.src = dataUrl; downloadLink.href = dataUrl; downloadLink.download = filename; outputDiv.style.display = 'block'; } catch (error) { console.error("Conversion error:", error); alert(`Error converting image. Format ${format} might not be supported by your browser for encoding or source image is problematic.`); } finally { hideLoading(); } }; img.onerror = () => { alert("Failed to load image. It might be corrupted or an unsupported format."); hideLoading(); }; img.src = e.target.result; }; reader.readAsDataURL(file); } }, imageCompressor: { process: () => { const fileInput = document.getElementById('imgCompFile'); const qualitySlider = document.getElementById('imgCompQuality'); const outputDiv = document.getElementById('imgCompOutput'); const resultImg = document.getElementById('imgCompResult'); const downloadLink = document.getElementById('imgCompDownload'); const infoP = document.getElementById('imgCompInfo'); if (!fileInput.files || fileInput.files.length === 0) { alert("Please select an image file."); return; } const file = fileInput.files[0]; const quality = parseFloat(qualitySlider.value); let outputFormat = 'image/jpeg'; // Default for compression if (file.type === 'image/png') outputFormat = 'image/png'; // PNG compression is lossless, quality ignored by most browsers but good to specify if (file.type === 'image/webp') outputFormat = 'image/webp'; // WEBP can use quality const filename = file.name.substring(0, file.name.lastIndexOf('.')) + '_compressed.' + outputFormat.split('/')[1]; showLoading("Compressing image..."); const reader = new FileReader(); reader.onload = (e) => { const originalSize = file.size; const img = new Image(); img.onload = () => { const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); try { const dataUrl = canvas.toDataURL(outputFormat, (outputFormat === 'image/png' ? undefined : quality)); resultImg.src = dataUrl; downloadLink.href = dataUrl; downloadLink.download = filename; // Estimate new size const newSize = dataUrl.length * 0.75; // Base64 to bytes approx infoP.textContent = `Original size: ${(originalSize / 1024).toFixed(2)} KB. Compressed size: ${(newSize / 1024).toFixed(2)} KB.`; outputDiv.style.display = 'block'; } catch (error) { console.error("Compression error:", error); alert(`Error compressing image. Format ${outputFormat} with quality ${quality} might not be supported, or source image is problematic.`); } finally { hideLoading(); } }; img.onerror = () => { alert("Failed to load image for compression."); hideLoading(); }; img.src = e.target.result; }; reader.readAsDataURL(file); } }, imageCropper: { img: null, canvas: null, ctx: null, cropBoxElement: null, resizers: [], cropRect: { x: 50, y: 50, width: 150, height: 100 }, isDragging: false, isResizing: false, resizeHandle: null, startX: 0, startY: 0, previewContainer: null, loadFile: (event) => { const C = ToolModules.imageCropper; const file = event.target.files[0]; if (!file) return; C.previewContainer = document.getElementById('imageCropperPreviewContainer'); C.canvas = document.getElementById('imageCropperCanvas'); C.ctx = C.canvas.getContext('2d'); C.cropBoxElement = document.getElementById('cropBox'); document.getElementById('imgCropBtn').style.display = 'inline-block'; document.getElementById('imgCropOutput').style.display = 'none'; const reader = new FileReader(); reader.onload = (e) => { C.img = new Image(); C.img.onload = () => { // Scale image to fit preview container while maintaining aspect ratio const containerWidth = C.previewContainer.clientWidth; const containerHeight = C.previewContainer.clientHeight; let canvasWidth = C.img.width; let canvasHeight = C.img.height; if (canvasWidth > containerWidth) { const ratio = containerWidth / canvasWidth; canvasWidth = containerWidth; canvasHeight *= ratio; } if (canvasHeight > containerHeight) { const ratio = containerHeight / canvasHeight; canvasHeight = containerHeight; canvasWidth *= ratio; } C.canvas.width = canvasWidth; C.canvas.height = canvasHeight; C.ctx.drawImage(C.img, 0, 0, canvasWidth, canvasHeight); C.previewContainer.style.display = 'block'; C.cropBoxElement.style.display = 'block'; C.initCropBox(); }; C.img.src = e.target.result; }; reader.readAsDataURL(file); }, initCropBox: () => { const C = ToolModules.imageCropper; C.cropRect = { x: C.canvas.width / 4, y: C.canvas.height / 4, width: C.canvas.width / 2, height: C.canvas.height / 2 }; C.updateCropBox(); C.cropBoxElement.onmousedown = C.onMouseDown; document.onmousemove = C.onMouseMove; // Attach to document to drag outside box document.onmouseup = C.onMouseUp; C.resizers = Array.from(C.cropBoxElement.querySelectorAll('.resizer')); C.resizers.forEach(resizer => { resizer.onmousedown = (e) => { e.stopPropagation(); // Prevent cropBox onMouseDown C.isResizing = true; C.resizeHandle = resizer.className.split(' ')[1]; // e.g., "top-left" C.startX = e.clientX; C.startY = e.clientY; }; }); }, updateCropBox: () => { const C = ToolModules.imageCropper; C.cropBoxElement.style.left = C.cropRect.x + 'px'; C.cropBoxElement.style.top = C.cropRect.y + 'px'; C.cropBoxElement.style.width = C.cropRect.width + 'px'; C.cropBoxElement.style.height = C.cropRect.height + 'px'; }, onMouseDown: (e) => { const C = ToolModules.imageCropper; if (e.target === C.cropBoxElement) { // Only if mousedown is on the box itself, not resizers C.isDragging = true; C.startX = e.clientX - C.cropRect.x; C.startY = e.clientY - C.cropRect.y; } }, onMouseMove: (e) => { const C = ToolModules.imageCropper; if (C.isDragging) { C.cropRect.x = e.clientX - C.startX; C.cropRect.y = e.clientY - C.startY; // Boundary checks C.cropRect.x = Math.max(0, Math.min(C.cropRect.x, C.canvas.width - C.cropRect.width)); C.cropRect.y = Math.max(0, Math.min(C.cropRect.y, C.canvas.height - C.cropRect.height)); C.updateCropBox(); } else if (C.isResizing) { const dx = e.clientX - C.startX; const dy = e.clientY - C.startY; let newX = C.cropRect.x, newY = C.cropRect.y, newWidth = C.cropRect.width, newHeight = C.cropRect.height; if (C.resizeHandle.includes('left')) { newWidth -= dx; newX += dx; } if (C.resizeHandle.includes('right')) { newWidth += dx; } if (C.resizeHandle.includes('top')) { newHeight -= dy; newY += dy; } if (C.resizeHandle.includes('bottom')) { newHeight += dy; } // Min size and boundary checks if (newWidth > 10 && newX >=0 && (newX + newWidth) <= C.canvas.width) { C.cropRect.x = newX; C.cropRect.width = newWidth; } if (newHeight > 10 && newY >=0 && (newY + newHeight) <= C.canvas.height) { C.cropRect.y = newY; C.cropRect.height = newHeight; } C.startX = e.clientX; C.startY = e.clientY; C.updateCropBox(); } }, onMouseUp: () => { ToolModules.imageCropper.isDragging = false; ToolModules.imageCropper.isResizing = false; }, crop: () => { const C = ToolModules.imageCropper; if (!C.img) { alert("Please load an image first."); return; } showLoading("Cropping image..."); // Calculate crop parameters based on original image dimensions const scaleX = C.img.naturalWidth / C.canvas.width; const scaleY = C.img.naturalHeight / C.canvas.height; const sourceX = C.cropRect.x * scaleX; const sourceY = C.cropRect.y * scaleY; const sourceWidth = C.cropRect.width * scaleX; const sourceHeight = C.cropRect.height * scaleY; const cropCanvas = document.createElement('canvas'); cropCanvas.width = sourceWidth; cropCanvas.height = sourceHeight; const cropCtx = cropCanvas.getContext('2d'); cropCtx.drawImage(C.img, sourceX, sourceY, sourceWidth, sourceHeight, 0, 0, sourceWidth, sourceHeight); const dataUrl = cropCanvas.toDataURL(C.img.src.startsWith('data:image/png') ? 'image/png' : 'image/jpeg'); document.getElementById('imgCropResult').src = dataUrl; document.getElementById('imgCropDownload').href = dataUrl; document.getElementById('imgCropDownload').download = "cropped_image.png"; // Or jpg document.getElementById('imgCropOutput').style.display = 'block'; hideLoading(); // Cleanup mouse listeners if tool is closed/reopened document.onmousemove = null; document.onmouseup = null; } }, videoConverter: { process: async () => { const fileInput = document.getElementById('vidConvFile'); const formatSelect = document.getElementById('vidConvFormat'); const outputDiv = document.getElementById('vidConvOutput'); const resultVideo = document.getElementById('vidConvResult'); const downloadLink = document.getElementById('vidConvDownload'); if (!fileInput.files || fileInput.files.length === 0) { alert("Please select a video file."); return; } const file = fileInput.files[0]; const outputMimeType = formatSelect.value; if (!MediaRecorder.isTypeSupported(outputMimeType)) { alert(`Your browser does not support recording to ${outputMimeType}. Try WebM.`); return; } showLoading("Preparing video for conversion..."); const video = document.createElement('video'); video.src = URL.createObjectURL(file); video.onloadedmetadata = async () => { const canvas = document.createElement('canvas'); canvas.width = video.videoWidth; canvas.height = video.videoHeight; const ctx = canvas.getContext('2d'); const stream = canvas.captureStream(30); // 30 FPS const mediaRecorder = new MediaRecorder(stream, { mimeType: outputMimeType }); const chunks = []; mediaRecorder.ondataavailable = (e) => chunks.push(e.data); mediaRecorder.onstop = () => { const blob = new Blob(chunks, { type: outputMimeType }); const url = URL.createObjectURL(blob); resultVideo.src = url; downloadLink.href = url; downloadLink.download = `converted.${outputMimeType.split('/')[1]}`; outputDiv.style.display = 'block'; hideLoading(); URL.revokeObjectURL(video.src); // Clean up }; let playing = false; video.onplay = () => playing = true; video.onpause = () => playing = false; // Should not pause during process function drawFrame() { if (!video.paused && !video.ended) { ctx.drawImage(video, 0, 0, canvas.width, canvas.height); requestAnimationFrame(drawFrame); } else if (video.ended && mediaRecorder.state === "recording") { mediaRecorder.stop(); } } video.onended = () => { if (mediaRecorder.state === "recording") { mediaRecorder.stop(); } }; try { await video.play(); showLoading(`Converting... Drawing frame ${Math.floor(video.currentTime)}s / ${Math.floor(video.duration)}s`); mediaRecorder.start(); drawFrame(); // Start drawing frames // Monitor progress const progressInterval = setInterval(() => { if (playing) { showLoading(`Converting... Frame at ${video.currentTime.toFixed(1)}s / ${video.duration.toFixed(1)}s`); } if (video.ended || mediaRecorder.state !== "recording") { clearInterval(progressInterval); } }, 500); } catch (err) { console.error("Error playing video for conversion:", err); alert("Could not start video playback for conversion. The video file might be corrupted or in an unsupported format."); hideLoading(); URL.revokeObjectURL(video.src); } }; video.onerror = () => { alert("Error loading video. File might be corrupted or unsupported."); hideLoading(); }; } }, audioConverter: { audioCtx: null, process: async () => { const fileInput = document.getElementById('audConvFile'); const outputDiv = document.getElementById('audConvOutput'); const resultAudio = document.getElementById('audConvResult'); const downloadLink = document.getElementById('audConvDownload'); if (!fileInput.files || fileInput.files.length === 0) { alert("Please select an audio file."); return; } const file = fileInput.files[0]; showLoading("Converting audio to WAV..."); if (!ToolModules.audioConverter.audioCtx) { ToolModules.audioConverter.audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } const audioCtx = ToolModules.audioConverter.audioCtx; const reader = new FileReader(); reader.onload = async (e) => { try { const audioBuffer = await audioCtx.decodeAudioData(e.target.result); const wavBlob = ToolModules.audioConverter.bufferToWav(audioBuffer, audioCtx.sampleRate); const url = URL.createObjectURL(wavBlob); resultAudio.src = url; downloadLink.href = url; outputDiv.style.display = 'block'; } catch (error) { console.error("Audio conversion error:", error); alert("Error converting audio. The file might be corrupted or in an unsupported format."); } finally { hideLoading(); } }; reader.readAsArrayBuffer(file); }, // Helper function to convert AudioBuffer to WAV Blob bufferToWav: (buffer, sampleRate) => { const numChannels = buffer.numberOfChannels; const numFrames = buffer.length; const length = numFrames * numChannels * 2 + 44; const wavBuffer = new ArrayBuffer(length); const view = new DataView(wavBuffer); let offset = 0; function writeString(str) { for (let i = 0; i < str.length; i++) { view.setUint8(offset + i, str.charCodeAt(i)); } offset += str.length; } function writeUint32(val) { view.setUint32(offset, val, true); offset += 4; } function writeUint16(val) { view.setUint16(offset, val, true); offset += 2; } writeString('RIFF'); writeUint32(length - 8); writeString('WAVE'); writeString('fmt '); writeUint32(16); // Subchunk1Size (16 for PCM) writeUint16(1); // AudioFormat (1 for PCM) writeUint16(numChannels); writeUint32(sampleRate); writeUint32(sampleRate * numChannels * 2); // ByteRate writeUint16(numChannels * 2); // BlockAlign writeUint16(16); // BitsPerSample writeString('data'); writeUint32(numFrames * numChannels * 2); // Subchunk2Size // Write PCM data for (let i = 0; i < numFrames; i++) { for (let channel = 0; channel < numChannels; channel++) { const sample = buffer.getChannelData(channel)[i]; let s = Math.max(-1, Math.min(1, sample)); s = s < 0 ? s * 0x8000 : s * 0x7FFF; view.setInt16(offset, s, true); offset += 2; } } return new Blob([view], { type: 'audio/wav' }); } }, audioTrimmer: { audioCtx: null, originalBuffer: null, loadFile: (event) => { const T = ToolModules.audioTrimmer; const file = event.target.files[0]; if (!file) return; showLoading("Loading audio..."); if (!T.audioCtx) { T.audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } const reader = new FileReader(); reader.onload = async (e) => { try { T.originalBuffer = await T.audioCtx.decodeAudioData(e.target.result); document.getElementById('audTrimControls').style.display = 'block'; document.getElementById('audTrimOutput').style.display = 'none'; const duration = T.originalBuffer.duration; document.getElementById('audTrimDuration').textContent = duration.toFixed(1); document.getElementById('audTrimEnd').value = duration.toFixed(1); document.getElementById('audTrimEnd').max = duration.toFixed(1); document.getElementById('audTrimStart').max = duration.toFixed(1); } catch (error) { alert("Error decoding audio file."); console.error(error); } finally { hideLoading(); } }; reader.readAsArrayBuffer(file); }, process: () => { const T = ToolModules.audioTrimmer; if (!T.originalBuffer) { alert("Please load an audio file first."); return; } const startTime = parseFloat(document.getElementById('audTrimStart').value); const endTime = parseFloat(document.getElementById('audTrimEnd').value); if (isNaN(startTime) || isNaN(endTime) || startTime < 0 || endTime <= startTime || endTime > T.originalBuffer.duration) { alert("Invalid start or end time."); return; } showLoading("Trimming audio..."); const startFrame = Math.floor(startTime * T.originalBuffer.sampleRate); const endFrame = Math.floor(endTime * T.originalBuffer.sampleRate); const newLength = endFrame - startFrame; if (newLength <= 0) { alert("Trimmed duration is zero or negative."); hideLoading(); return; } const trimmedBuffer = T.audioCtx.createBuffer( T.originalBuffer.numberOfChannels, newLength, T.originalBuffer.sampleRate ); for (let i = 0; i < T.originalBuffer.numberOfChannels; i++) { const channelData = T.originalBuffer.getChannelData(i); const trimmedChannelData = trimmedBuffer.getChannelData(i); trimmedChannelData.set(channelData.subarray(startFrame, endFrame)); } const wavBlob = ToolModules.audioConverter.bufferToWav(trimmedBuffer, T.audioCtx.sampleRate); // Reuse WAV conversion const url = URL.createObjectURL(wavBlob); document.getElementById('audTrimResult').src = url; document.getElementById('audTrimDownload').href = url; document.getElementById('audTrimOutput').style.display = 'block'; hideLoading(); } }, ageCalculator: { calculate: () => { const birthDateInput = document.getElementById('birthDate').value; const resultDiv = document.getElementById('ageResult'); if (!birthDateInput) { resultDiv.textContent = "Please enter your date of birth."; resultDiv.style.display = 'block'; return; } const birthDate = new Date(birthDateInput); const today = new Date(); let years = today.getFullYear() - birthDate.getFullYear(); let months = today.getMonth() - birthDate.getMonth(); let days = today.getDate() - birthDate.getDate(); if (days < 0) { months--; days += new Date(today.getFullYear(), today.getMonth(), 0).getDate(); // Days in previous month } if (months < 0) { years--; months += 12; } resultDiv.innerHTML = `${years} years, ${months} months, and ${days} days
`; resultDiv.style.display = 'block'; } }, emiCalculator: { calculate: () => { const P = parseFloat(document.getElementById('loanAmount').value); const annualRate = parseFloat(document.getElementById('interestRate').value); const tenureYears = parseFloat(document.getElementById('loanTenure').value); const resultDiv = document.getElementById('emiResult'); if (isNaN(P) || isNaN(annualRate) || isNaN(tenureYears) || P <= 0 || annualRate <= 0 || tenureYears <= 0) { resultDiv.textContent = "Please enter valid positive values for all fields."; resultDiv.style.display = 'block'; return; } const r = (annualRate / 100) / 12; // Monthly interest rate const n = tenureYears * 12; // Number of months const emi = P * r * (Math.pow(1 + r, n) / (Math.pow(1 + r, n) - 1)); const totalAmountPayable = emi * n; const totalInterest = totalAmountPayable - P; if (!isFinite(emi)) { resultDiv.textContent = "Calculation resulted in an invalid EMI. Check inputs, especially interest rate if it's too low or tenure is very short."; } else { resultDiv.innerHTML = `Monthly EMI: ${emi.toFixed(2)}
Total Interest Payable: ${totalInterest.toFixed(2)}
Total Amount Payable: ${totalAmountPayable.toFixed(2)}
`; } resultDiv.style.display = 'block'; } }, sipCalculator: { calculate: () => { const P = parseFloat(document.getElementById('monthlyInvestment').value); const annualRate = parseFloat(document.getElementById('expectedRate').value); const years = parseFloat(document.getElementById('investmentDuration').value); const resultDiv = document.getElementById('sipResult'); if (isNaN(P) || isNaN(annualRate) || isNaN(years) || P <= 0 || annualRate < 0 || years <= 0) { resultDiv.textContent = "Please enter valid positive values for all fields."; resultDiv.style.display = 'block'; return; } const n = years * 12; // Number of months const i = (annualRate / 100) / 12; // Monthly interest rate // M = P * {[(1+i)^n - 1] / i} * (1+i) const futureValue = P * ( (Math.pow(1 + i, n) - 1) / i ) * (1 + i); const totalInvested = P * n; const wealthGained = futureValue - totalInvested; resultDiv.innerHTML = `Total Invested Amount: ${totalInvested.toFixed(2)}
Estimated Returns (Wealth Gained): ${wealthGained.toFixed(2)}
Future Value (Maturity Amount): ${futureValue.toFixed(2)}
`; resultDiv.style.display = 'block'; } }, qrCodeGenerator: { generate: () => { const text = document.getElementById('qrText').value; const outputDiv = document.getElementById('qrOutput'); const canvasContainer = document.getElementById('qrCanvasContainer'); const downloadLink = document.getElementById('qrDownload'); if (!text.trim()) { alert("Please enter text or URL for the QR code."); return; } showLoading("Generating QR Code..."); canvasContainer.innerHTML = ''; // Clear previous QR try { // Using the embedded qrcodegen const qr = qrcodegen.QrCode.encodeText(text, qrcodegen.QrCode.Ecc.MEDIUM); // Ecc is not fully implemented in this stub const qrCanvasGenerator = new qrcodegen.QRCanvas({ scale: 6, padding: 20, fgColor: getComputedStyle(document.documentElement).getPropertyValue('--accent-color').trim(), // Gold bgColor: getComputedStyle(document.documentElement).getPropertyValue('--bg-color').trim() // Dark Navy }); const canvasElement = qrCanvasGenerator.draw(qr); canvasContainer.appendChild(canvasElement); downloadLink.href = canvasElement.toDataURL('image/png'); outputDiv.style.display = 'block'; } catch (e) { console.error("QR Generation Error:", e); alert("Failed to generate QR code. The embedded QR generator is very basic and might not support long text or certain characters well."); outputDiv.style.display = 'none'; } finally { hideLoading(); } } }, passwordGenerator: { generate: () => { const length = parseInt(document.getElementById('passLength').value); const useUppercase = document.getElementById('passUppercase').checked; const useLowercase = document.getElementById('passLowercase').checked; const useNumbers = document.getElementById('passNumbers').checked; const useSymbols = document.getElementById('passSymbols').checked; const passwordField = document.getElementById('generatedPassword'); const resultDiv = document.getElementById('passResult'); const uppercaseChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; const lowercaseChars = "abcdefghijklmnopqrstuvwxyz"; const numberChars = "0123456789"; const symbolChars = "!@#$%^&*()_+-=[]{}|;:',.<>/?"; let charPool = ""; if (useUppercase) charPool += uppercaseChars; if (useLowercase) charPool += lowercaseChars; if (useNumbers) charPool += numberChars; if (useSymbols) charPool += symbolChars; if (charPool === "") { alert("Please select at least one character type."); passwordField.value = ""; resultDiv.style.display = 'none'; return; } let password = ""; for (let i = 0; i < length; i++) { const randomIndex = Math.floor(Math.random() * charPool.length); password += charPool[randomIndex]; } passwordField.value = password; resultDiv.style.display = 'block'; // Ensure copy button is available if (!document.getElementById('copyPassBtn')) { const copyBtn = document.createElement('button'); copyBtn.id = 'copyPassBtn'; copyBtn.className = 'btn'; copyBtn.textContent = 'Copy'; copyBtn.addEventListener('click', ToolModules.passwordGenerator.copy); passwordField.parentNode.appendChild(copyBtn); } }, copy: () => { const passwordField = document.getElementById('generatedPassword'); passwordField.select(); passwordField.setSelectionRange(0, 99999); // For mobile devices try { document.execCommand('copy'); alert("Password copied to clipboard!"); } catch (err) { alert("Failed to copy password. Please copy it manually."); } } }, wordCounter: { count: () => { const text = document.getElementById('wordCountText').value; const words = text.match(/\b\w+\b/g) || []; const charsWithSpace = text.length; const charsNoSpace = text.replace(/\s/g, "").length; const spaces = (text.match(/\s/g) || []).length; const readingTime = Math.ceil(words.length / 200); // Avg 200 WPM document.getElementById('wcWords').textContent = words.length; document.getElementById('wcCharsWithSpace').textContent = charsWithSpace; document.getElementById('wcCharsNoSpace').textContent = charsNoSpace; document.getElementById('wcSpaces').textContent = spaces; document.getElementById('wcReadTime').textContent = readingTime; } }, base64EncoderDecoder: { encode: () => { const input = document.getElementById('b64Input').value; const outputField = document.getElementById('b64Output'); try { // Handle Unicode characters correctly const utf8Bytes = new TextEncoder().encode(input); let binaryString = ''; utf8Bytes.forEach(byte => binaryString += String.fromCharCode(byte)); outputField.value = btoa(binaryString); } catch (e) { outputField.value = "Error encoding: " + e.message; } }, decode: () => { const input = document.getElementById('b64Input').value; const outputField = document.getElementById('b64Output'); try { const binaryString = atob(input); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } outputField.value = new TextDecoder().decode(bytes); } catch (e) { outputField.value = "Error decoding: Invalid Base64 string or " + e.message; } } }, colorPicker: { update: () => { const hex = document.getElementById('colorPickerInput').value; document.getElementById('hexValue').textContent = hex; const rgb = ToolModules.colorPicker.hexToRgb(hex); document.getElementById('rgbValue').textContent = `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`; const hsl = ToolModules.colorPicker.rgbToHsl(rgb.r, rgb.g, rgb.b); document.getElementById('hslValue').textContent = `hsl(${hsl.h.toFixed(0)}, ${hsl.s.toFixed(0)}%, ${hsl.l.toFixed(0)}%)`; }, hexToRgb: (hex) => { hex = hex.replace(/^#/, ''); const bigint = parseInt(hex, 16); return { r: (bigint >> 16) & 255, g: (bigint >> 8) & 255, b: bigint & 255 }; }, rgbToHsl: (r, g, b) => { r /= 255; g /= 255; b /= 255; const max = Math.max(r, g, b), min = Math.min(r, g, b); let h, s, l = (max + min) / 2; if (max === min) { h = s = 0; // achromatic } else { const d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } h /= 6; } return { h: h * 360, s: s * 100, l: l * 100 }; } }, textToSpeech: { synth: window.speechSynthesis, voices: [], initVoices: () => { const T = ToolModules.textToSpeech; const voiceSelect = document.getElementById('ttsVoice'); if (!T.synth) { alert("Speech synthesis not supported in this browser."); document.getElementById('ttsSpeakBtn').disabled = true; document.getElementById('ttsStopBtn').disabled = true; return; } function populateVoiceList() { T.voices = T.synth.getVoices(); voiceSelect.innerHTML = ''; T.voices.forEach(voice => { const option = document.createElement('option'); option.textContent = `${voice.name} (${voice.lang})`; option.setAttribute('data-lang', voice.lang); option.setAttribute('data-name', voice.name); voiceSelect.appendChild(option); }); } populateVoiceList(); if (T.synth.onvoiceschanged !== undefined) { T.synth.onvoiceschanged = populateVoiceList; } }, speak: () => { const T = ToolModules.textToSpeech; if (T.synth.speaking) { T.synth.cancel(); // Cancel previous before speaking new } const text = document.getElementById('ttsText').value; if (!text.trim()) { alert("Please enter some text to speak."); return; } const utterThis = new SpeechSynthesisUtterance(text); const selectedVoiceName = document.getElementById('ttsVoice').selectedOptions[0]?.getAttribute('data-name'); if (selectedVoiceName) { utterThis.voice = T.voices.find(voice => voice.name === selectedVoiceName); } T.synth.speak(utterThis); }, stop: () => { ToolModules.textToSpeech.synth.cancel(); } }, speechToText: { recognition: null, init: () => { const S = ToolModules.speechToText; const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; if (!SpeechRecognition) { document.getElementById('sttStatus').textContent = "Speech recognition not supported in this browser."; document.getElementById('sttStartBtn').disabled = true; return; } S.recognition = new SpeechRecognition(); S.recognition.continuous = false; // Set to true for continuous listening S.recognition.lang = 'en-US'; // Default language S.recognition.interimResults = true; // Show interim results S.recognition.onstart = () => { document.getElementById('sttStatus').textContent = "Listening..."; document.getElementById('sttStartBtn').disabled = true; document.getElementById('sttStopBtn').disabled = false; }; S.recognition.onresult = (event) => { let interimTranscript = ''; let finalTranscript = ''; for (let i = event.resultIndex; i < event.results.length; ++i) { if (event.results[i].isFinal) { finalTranscript += event.results[i][0].transcript; } else { interimTranscript += event.results[i][0].transcript; } } document.getElementById('sttResult').value = finalTranscript + interimTranscript; }; S.recognition.onerror = (event) => { document.getElementById('sttStatus').textContent = `Error: ${event.error}`; document.getElementById('sttStartBtn').disabled = false; document.getElementById('sttStopBtn').disabled = true; }; S.recognition.onend = () => { document.getElementById('sttStatus').textContent = "Status: Idle (stopped)"; document.getElementById('sttStartBtn').disabled = false; document.getElementById('sttStopBtn').disabled = true; }; }, start: () => { const S = ToolModules.speechToText; if (S.recognition) { document.getElementById('sttResult').value = ''; // Clear previous results S.recognition.start(); } else { document.getElementById('sttStatus').textContent = "Speech recognition not initialized."; } }, stop: () => { const S = ToolModules.speechToText; if (S.recognition) { S.recognition.stop(); } } }, jsonFormatter: { format: () => { const input = document.getElementById('jsonInput').value; const outputField = document.getElementById('jsonOutput'); const errorP = document.getElementById('jsonError'); errorP.textContent = ''; try { const parsedJson = JSON.parse(input); outputField.value = JSON.stringify(parsedJson, null, 2); // 2 spaces for indentation } catch (e) { outputField.value = "Invalid JSON: " + e.message; errorP.textContent = "Error: " + e.message; } } }, unitConverter: { units: { length: { meter: 1, kilometer: 1000, centimeter: 0.01, millimeter: 0.001, mile: 1609.34, yard: 0.9144, foot: 0.3048, inch: 0.0254 }, weight: { kilogram: 1, gram: 0.001, milligram: 0.000001, pound: 0.453592, ounce: 0.0283495 }, temperature: { // Special handling needed celsius: 'celsius', fahrenheit: 'fahrenheit', kelvin: 'kelvin' } }, init: () => { const U = ToolModules.unitConverter; const category = document.getElementById('unitCategory').value; const fromSelect = document.getElementById('unitFrom'); const toSelect = document.getElementById('unitTo'); fromSelect.innerHTML = ''; toSelect.innerHTML = ''; for (const unit in U.units[category]) { const option1 = document.createElement('option'); option1.value = unit; option1.textContent = unit.charAt(0).toUpperCase() + unit.slice(1); fromSelect.appendChild(option1); const option2 = document.createElement('option'); option2.value = unit; option2.textContent = unit.charAt(0).toUpperCase() + unit.slice(1); toSelect.appendChild(option2); } if (category === "length") { // Set defaults for length fromSelect.value = "meter"; toSelect.value = "kilometer"; } else if (category === "weight") { fromSelect.value = "kilogram"; toSelect.value = "gram"; } else if (category === "temperature") { fromSelect.value = "celsius"; toSelect.value = "fahrenheit"; } U.convert(); }, convert: () => { const U = ToolModules.unitConverter; const category = document.getElementById('unitCategory').value; const inputValue = parseFloat(document.getElementById('unitInput').value); const fromUnit = document.getElementById('unitFrom').value; const toUnit = document.getElementById('unitTo').value; const outputField = document.getElementById('unitOutput'); if (isNaN(inputValue)) { outputField.value = ""; return; } let result; if (category === 'temperature') { result = U.convertTemperature(inputValue, fromUnit, toUnit); } else { const baseValue = inputValue * U.units[category][fromUnit]; // Convert to base unit (meter or kg) result = baseValue / U.units[category][toUnit]; // Convert from base to target unit } outputField.value = result.toFixed(5); // Adjust precision as needed }, convertTemperature: (value, from, to) => { if (from === to) return value; let celsius; // Convert input to Celsius first if (from === 'fahrenheit') celsius = (value - 32) * 5/9; else if (from === 'kelvin') celsius = value - 273.15; else celsius = value; // Already Celsius // Convert Celsius to target unit if (to === 'fahrenheit') return (celsius * 9/5) + 32; if (to === 'kelvin') return celsius + 273.15; return celsius; // Target is Celsius } }, bmiCalculator: { calculate: () => { const weight = parseFloat(document.getElementById('bmiWeight').value); const heightCm = parseFloat(document.getElementById('bmiHeight').value); const resultDiv = document.getElementById('bmiResult'); if (isNaN(weight) || isNaN(heightCm) || weight <= 0 || heightCm <= 0) { resultDiv.textContent = "Please enter valid positive values for weight and height."; resultDiv.style.display = 'block'; return; } const heightM = heightCm / 100; const bmi = weight / (heightM * heightM); let category = ""; if (bmi < 18.5) category = "Underweight"; else if (bmi < 24.9) category = "Normal weight"; else if (bmi < 29.9) category = "Overweight"; else category = "Obesity"; resultDiv.innerHTML = `BMI Value: ${bmi.toFixed(2)}
Category: ${category}
`; resultDiv.style.display = 'block'; } }, timerStopwatch: { stopwatch: { interval: null, startTime: 0, elapsedTime: 0, running: false }, timer: { interval: null, endTime: 0, running: false, duration: 0 }, alarmSound: null, init: () => { const TS = ToolModules.timerStopwatch; TS.alarmSound = document.getElementById('timerAlarm'); // Stopwatch buttons document.getElementById('tsStartBtn').onclick = TS.startStopwatch; document.getElementById('tsStopBtn').onclick = TS.stopStopwatch; document.getElementById('tsResetBtn').onclick = TS.resetStopwatch; // Timer buttons document.getElementById('timerStartBtn').onclick = TS.startTimer; document.getElementById('timerStopBtn').onclick = TS.stopTimer; document.getElementById('timerResetBtn').onclick = TS.resetTimer; TS.updateDisplay(0); // Initial stopwatch display }, formatTime: (ms) => { const totalSeconds = Math.floor(ms / 1000); const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; const milliseconds = Math.floor((ms % 1000) / 100); // Tenths of a second return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}:${String(milliseconds).padStart(1,'0')}`; }, formatTimerTime: (ms) => { if (ms < 0) ms = 0; const totalSeconds = Math.floor(ms / 1000); const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`; }, updateDisplay: (timeMs, isTimer = false) => { document.getElementById('tsDisplay').textContent = isTimer ? ToolModules.timerStopwatch.formatTimerTime(timeMs) : ToolModules.timerStopwatch.formatTime(timeMs); }, // Stopwatch startStopwatch: () => { const S = ToolModules.timerStopwatch.stopwatch; if (S.running) return; S.running = true; S.startTime = Date.now() - S.elapsedTime; S.interval = setInterval(() => { S.elapsedTime = Date.now() - S.startTime; ToolModules.timerStopwatch.updateDisplay(S.elapsedTime); }, 100); // Update every 100ms for smooth tenth of second }, stopStopwatch: () => { const S = ToolModules.timerStopwatch.stopwatch; if (!S.running) return; S.running = false; clearInterval(S.interval); S.elapsedTime = Date.now() - S.startTime; // Final accurate time ToolModules.timerStopwatch.updateDisplay(S.elapsedTime); }, resetStopwatch: () => { const S = ToolModules.timerStopwatch.stopwatch; ToolModules.timerStopwatch.stopStopwatch(); // Stop if running S.elapsedTime = 0; ToolModules.timerStopwatch.updateDisplay(0); }, // Timer startTimer: () => { const T = ToolModules.timerStopwatch.timer; const TS = ToolModules.timerStopwatch; if (T.running) return; const hours = parseInt(document.getElementById('timerHours').value) || 0; const minutes = parseInt(document.getElementById('timerMinutes').value) || 0; const seconds = parseInt(document.getElementById('timerSeconds').value) || 0; T.duration = (hours * 3600 + minutes * 60 + seconds) * 1000; if (T.duration <= 0) { alert("Please set a valid timer duration."); return; } T.running = true; T.endTime = Date.now() + T.duration; TS.updateDisplay(T.duration, true); T.interval = setInterval(() => { const timeLeft = T.endTime - Date.now(); if (timeLeft <= 0) { TS.stopTimer(); TS.updateDisplay(0, true); TS.alarmSound.play(); alert("Timer Finished!"); // Optionally stop alarm after a few seconds: setTimeout(() => TS.alarmSound.pause(), 5000); return; } TS.updateDisplay(timeLeft, true); }, 1000); }, stopTimer: () => { const T = ToolModules.timerStopwatch.timer; const TS = ToolModules.timerStopwatch; if (!T.running) return; T.running = false; clearInterval(T.interval); T.duration = T.endTime - Date.now(); // Save remaining duration if (T.duration < 0) T.duration = 0; TS.alarmSound.pause(); TS.alarmSound.currentTime = 0; }, resetTimer: () => { const T = ToolModules.timerStopwatch.timer; const TS = ToolModules.timerStopwatch; TS.stopTimer(); T.duration = 0; document.getElementById('timerHours').value = "0"; document.getElementById('timerMinutes').value = "0"; document.getElementById('timerSeconds').value = "0"; TS.updateDisplay(0, true); // Reset display for timer section } } }; // Expose ToolModules if any tool needs to call another (e.g. audioConverter.bufferToWav) // window.ToolModules = ToolModules; // Not strictly needed if modules are self-contained or pass `this` }); // End DOMContentLoaded ```