Web Radio

Onic Computer – Multi Tools Hub

Welcome to the Future of Frontend Tools

A comprehensive suite of 35+ utilities, built with a sleek, futuristic interface. No backend, no libraries, just pure performance.

Image Converter

Convert images to JPG, PNG, or WEBP formats.

Image Compressor

Reduce image file size with adjustable quality.

Image Cropper

Crop and resize your images with a simple interface.

Video Recorder

Record video from your webcam (browser-based).

Audio Recorder

Record audio from your microphone in various formats.

Audio Trimmer

Trim audio files directly in your browser.

Image to PDF

Convert JPG, PNG, and other images into a PDF document.

Merge PDF

Combine multiple PDF files into a single document.

Split PDF

Extract a range of pages from a PDF file.

Text Reverser

Flip your text backward, character by character.

To-Do List

A simple, session-based task manager to keep you on track.

Password Checker

Analyze the strength of your password instantly.

Age Calculator

Calculate age from a given date of birth.

EMI Calculator

Calculate Equated Monthly Installment for loans.

SIP Calculator

Calculate returns on your Systematic Investment Plan.

Calculator

A standard calculator for your everyday math needs.

Discount Calculator

Quickly find the final price after a discount.

Tip Calculator

Calculate tips and split the bill between friends.

Date Calculator

Find the duration between two dates or add/subtract days.

Random Number Generator

Generate a random number within a specified range.

World Time

View the current time in major cities around the globe.

QR Code Generator

Create QR codes for URLs, text, and more.

Password Generator

Generate strong, secure, and random passwords.

Word Counter

Count words, characters, and sentences in your text.

Base64 Encoder/Decoder

Encode text to Base64 or decode from it.

Color Picker Tool

Pick colors and get their HEX, RGB, and HSL values.

Text to Speech

Convert your text into natural-sounding speech.

Speech to Text

Transcribe your spoken words into text.

JSON Formatter

Format, validate, and beautify your JSON data.

Unit Converter

Convert between various units (length, weight, etc.).

BMI Calculator

Calculate your Body Mass Index (BMI).

Timer / Stopwatch

A simple, clean timer and stopwatch utility.

Case Converter

Convert text to UPPERCASE, lowercase, Title Case, etc.

Lorem Ipsum Generator

Generate placeholder text for your designs.

URL Encoder/Decoder

Encode text for URLs or decode it back.

About Us

Learn more about the Onic Computer project.

© 2025 Onic Computer. All rights reserved. | Onic Agyat

Your list is saved for this session only. It will reset when you close this tool.

    `, init: () => { const input = document.getElementById('todo-input'); const addBtn = document.getElementById('add-todo-btn'); const list = document.getElementById('todo-list'); const addTodo = () => { const taskText = input.value.trim(); if (taskText === '') return; const li = document.createElement('li'); const span = document.createElement('span'); span.textContent = taskText; span.onclick = () => li.classList.toggle('completed'); const deleteBtn = document.createElement('button'); deleteBtn.innerHTML = '×'; deleteBtn.onclick = () => li.remove(); li.appendChild(span); li.appendChild(deleteBtn); list.appendChild(li); input.value = ''; input.focus(); }; addBtn.addEventListener('click', addTodo); input.addEventListener('keypress', e => { if (e.key === 'Enter') addTodo(); }); } }, passwordChecker: { title: 'Password Strength Checker', icon: ICONS.lock, getUI: () => `
    `, init: () => { const input = document.getElementById('password-check-input'); const fill = document.getElementById('password-strength-fill'); const text = document.getElementById('password-strength-text'); input.addEventListener('input', () => { const pass = input.value; let score = 0; if (pass.length > 8) score++; if (pass.length > 12) score++; if (/[A-Z]/.test(pass)) score++; if (/[a-z]/.test(pass)) score++; if (/[0-9]/.test(pass)) score++; if (/[^A-Za-z0-9]/.test(pass)) score++; let strength = "Very Weak"; let color = 'var(--danger-color)'; let width = '15%'; if (score > 2) { strength = "Weak"; width = '35%'; color = 'var(--danger-color)'; } if (score > 3) { strength = "Medium"; width = '60%'; color = 'var(--warning-color)'; } if (score > 4) { strength = "Strong"; width = '80%'; color = 'var(--success-color)'; } if (score > 5) { strength = "Very Strong"; width = '100%'; color = 'var(--primary-accent)'; } if (pass.length === 0) { strength = ""; width = '0%'; } fill.style.width = width; fill.style.backgroundColor = color; text.textContent = strength; }); } }, // --- NEW CALCULATOR TOOLS --- calculator: { title: 'Calculator', icon: ICONS.calculator, getUI: () => `
    0
    `, init: () => { const display = document.getElementById('calc-display'); let currentInput = '0'; let operator = null; let previousInput = null; document.querySelector('.calc-grid').addEventListener('click', e => { if (!e.target.matches('.calc-btn')) return; const key = e.target.dataset.key; if (/\d/.test(key)) { currentInput = currentInput === '0' ? key : currentInput + key; } else if (key === '.') { if (!currentInput.includes('.')) currentInput += '.'; } else if (key === 'clear') { currentInput = '0'; operator = null; previousInput = null; } else if (key === 'backspace') { currentInput = currentInput.slice(0, -1) || '0'; } else if (key === '=') { if (operator && previousInput !== null) { currentInput = String(eval(`${previousInput} ${operator} ${currentInput}`)); operator = null; previousInput = null; } } else { // Operator if (operator && previousInput !== null) { currentInput = String(eval(`${previousInput} ${operator} ${currentInput}`)); } operator = key; previousInput = currentInput; currentInput = '0'; } display.textContent = currentInput; }); } }, discountCalculator: { title: 'Discount Calculator', icon: ICONS.tag, getUI: () => `
    `, init: () => { const priceInput = document.getElementById('dc-price'); const discountInput = document.getElementById('dc-discount'); const output = document.getElementById('dc-output'); function calculate() { const price = parseFloat(priceInput.value); const discount = parseFloat(discountInput.value); if (isNaN(price) || isNaN(discount)) { output.innerHTML = 'Enter values to see result.'; return; } const saved = price * (discount / 100); const finalPrice = price - saved; output.innerHTML = `

    You Save: $${saved.toFixed(2)}

    Final Price: $${finalPrice.toFixed(2)}

    `; } [priceInput, discountInput].forEach(el => el.addEventListener('input', calculate)); } }, tipCalculator: { title: 'Tip Calculator', icon: ICONS.calculator, getUI: () => `
    `, init: () => { const billInput = document.getElementById('tip-bill'); const percentInput = document.getElementById('tip-percent'); const peopleInput = document.getElementById('tip-people'); const output = document.getElementById('tip-output'); function calculate() { const bill = parseFloat(billInput.value); const percent = parseFloat(percentInput.value); const people = parseInt(peopleInput.value, 10); if (isNaN(bill) || isNaN(percent) || isNaN(people) || people < 1) { output.innerHTML = 'Enter valid values.'; return; } const totalTip = bill * (percent / 100); const totalBill = bill + totalTip; const tipPerPerson = totalTip / people; const totalPerPerson = totalBill / people; output.innerHTML = `

    Tip per Person: $${tipPerPerson.toFixed(2)}

    Total per Person: $${totalPerPerson.toFixed(2)}


    Total Tip: $${totalTip.toFixed(2)}

    Total Bill: $${totalBill.toFixed(2)}

    `; } [billInput, percentInput, peopleInput].forEach(el => el.addEventListener('input', calculate)); } }, // --- NEW DATE & TIME TOOLS --- dateCalculator: { title: 'Date Calculator', icon: ICONS.calendar, getUI: () => `
    `, init: () => { document.getElementById('date-start').valueAsDate = new Date(); document.getElementById('date-end').valueAsDate = new Date(); document.getElementById('date-calc-btn').addEventListener('click', () => { const start = new Date(document.getElementById('date-start').value); const end = new Date(document.getElementById('date-end').value); const output = document.getElementById('date-output'); if (isNaN(start) || isNaN(end)) { output.innerHTML = 'Please select valid dates.'; return; } const diffTime = Math.abs(end - start); const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); output.innerHTML = `The difference is ${diffDays} days.`; }); } }, randomNumberGenerator: { title: 'Random Number Generator', icon: ICONS.dice, getUI: () => `
    `, init: () => { const minInput = document.getElementById('rand-min'); const maxInput = document.getElementById('rand-max'); const output = document.getElementById('rand-output'); const genBtn = document.getElementById('rand-gen-btn'); const generate = () => { const min = parseInt(minInput.value, 10); const max = parseInt(maxInput.value, 10); if (min > max) { output.textContent = 'Min > Max!'; return; } const rand = Math.floor(Math.random() * (max - min + 1)) + min; output.textContent = rand; }; genBtn.addEventListener('click', generate); generate(); } }, worldTime: { title: 'World Time', icon: ICONS.clock, getUI: () => `

    Showing current times in different time zones.

    `, init: () => { const container = document.getElementById('world-clock-container'); const timeZones = [ { city: 'New York', zone: 'America/New_York' }, { city: 'London', zone: 'Europe/London' }, { city: 'Tokyo', zone: 'Asia/Tokyo' }, { city: 'Sydney', zone: 'Australia/Sydney' }, { city: 'Dubai', zone: 'Asia/Dubai' }, { city: 'Los Angeles', zone: 'America/Los_Angeles' }, ]; function updateClocks() { container.innerHTML = ''; const now = new Date(); timeZones.forEach(tz => { const time = now.toLocaleTimeString('en-US', { timeZone: tz.zone, hour: '2-digit', minute: '2-digit', second: '2-digit' }); const date = now.toLocaleDateString('en-US', { timeZone: tz.zone, weekday: 'long', month: 'short', day: 'numeric' }); const clockEl = document.createElement('div'); clockEl.className = 'world-clock'; clockEl.innerHTML = `
    ${tz.city}
    ${time}
    ${date}
    `; container.appendChild(clockEl); }); } updateClocks(); currentInterval = setInterval(updateClocks, 1000); } }, // --- EXISTING TOOLS (No changes below this line, just for context) --- imageConverter: { title: 'Image Converter', getUI: () => `
    `, init: () => { const upload = document.getElementById('image-upload'); const convertBtn = document.getElementById('convert-btn'); const formatSelect = document.getElementById('format-select'); const output = document.getElementById('output-container'); let file = null; upload.addEventListener('change', e => file = e.target.files[0]); convertBtn.addEventListener('click', () => { if (!file) { alert('Please upload an image first.'); return; } output.innerHTML = 'Processing...'; 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); const format = formatSelect.value; const ext = format.split('/')[1]; canvas.toBlob(blob => { const newFilename = `${file.name.split('.')[0]}.${ext}`; output.innerHTML = ''; output.appendChild(createDownloadLink(blob, newFilename, `Download as ${ext.toUpperCase()}`)); }, format, 0.95); }; img.src = e.target.result; }; reader.readAsDataURL(file); }); } }, imageCompressor: { title: 'Image Compressor', getUI: () => `
    0.7
    `, init: () => { const upload = document.getElementById('compress-upload'); const compressBtn = document.getElementById('compress-btn'); const qualitySlider = document.getElementById('quality-slider'); const qualityValue = document.getElementById('quality-value'); const output = document.getElementById('compress-output'); let file = null; upload.addEventListener('change', e => file = e.target.files[0]); qualitySlider.addEventListener('input', e => qualityValue.textContent = e.target.value); compressBtn.addEventListener('click', () => { if (!file) { alert('Please upload an image first.'); return; } output.innerHTML = 'Compressing...'; 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); const quality = parseFloat(qualitySlider.value); canvas.toBlob(blob => { const originalSize = (file.size / 1024).toFixed(2); const newSize = (blob.size / 1024).toFixed(2); output.innerHTML = `Original Size: ${originalSize} KB | New Size: ${newSize} KB

    `; output.appendChild(createDownloadLink(blob, `compressed_${file.name}`, 'Download Compressed Image')); }, 'image/jpeg', quality); }; img.src = e.target.result; }; reader.readAsDataURL(file); }); } }, imageCropper: { title: 'Image Cropper', getUI: () => `

    Click and drag on the image to select a crop area.

    `, init: () => { const upload = document.getElementById('crop-upload'); const canvas = document.getElementById('crop-canvas'); const ctx = canvas.getContext('2d'); const cropBtn = document.getElementById('crop-btn'); let img, selection = { startX: 0, startY: 0, w: 0, h: 0 }, dragging = false; upload.addEventListener('change', e => { const reader = new FileReader(); reader.onload = res => { img = new Image(); img.onload = () => { const MAX_WIDTH = 500; const scale = MAX_WIDTH / img.width; canvas.width = MAX_WIDTH; canvas.height = img.height * scale; ctx.drawImage(img, 0, 0, canvas.width, canvas.height); }; img.src = res.target.result; }; reader.readAsDataURL(e.target.files[0]); }); canvas.addEventListener('mousedown', e => { dragging = true; selection.startX = e.offsetX; selection.startY = e.offsetY; }); canvas.addEventListener('mousemove', e => { if (!dragging) return; ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); selection.w = e.offsetX - selection.startX; selection.h = e.offsetY - selection.startY; ctx.strokeStyle = 'red'; ctx.strokeRect(selection.startX, selection.startY, selection.w, selection.h); }); canvas.addEventListener('mouseup', () => dragging = false); cropBtn.addEventListener('click', () => { if (!img || selection.w === 0 || selection.h === 0) { alert('Please upload an image and select an area.'); return; } const scaleX = img.naturalWidth / canvas.width; const scaleY = img.naturalHeight / canvas.height; const cropCanvas = document.createElement('canvas'); cropCanvas.width = Math.abs(selection.w * scaleX); cropCanvas.height = Math.abs(selection.h * scaleY); const cropCtx = cropCanvas.getContext('2d'); cropCtx.drawImage( img, Math.min(selection.startX, selection.startX + selection.w) * scaleX, Math.min(selection.startY, selection.startY + selection.h) * scaleY, Math.abs(selection.w * scaleX), Math.abs(selection.h * scaleY), 0, 0, cropCanvas.width, cropCanvas.height ); cropCanvas.toBlob(blob => { createDownloadLink(blob, 'cropped-image.png', 'Download Cropped Image').click(); }, 'image/png'); }); } }, videoConverter: { title: 'Video Recorder (Webcam)', getUI: () => `

    Note: This tool records video from your webcam. True file conversion is not possible without backend or heavy libraries.

    `, init: () => { const preview = document.getElementById('video-preview'); const recordBtn = document.getElementById('record-btn'); const output = document.getElementById('video-output'); let mediaRecorder, chunks = []; navigator.mediaDevices.getUserMedia({ video: true, audio: true }) .then(stream => { preview.srcObject = stream; recordBtn.onclick = () => { if (!mediaRecorder || mediaRecorder.state === 'inactive') { mediaRecorder = new MediaRecorder(stream, { mimeType: 'video/webm' }); mediaRecorder.ondataavailable = e => chunks.push(e.data); mediaRecorder.onstop = () => { const blob = new Blob(chunks, { type: 'video/webm' }); chunks = []; output.innerHTML = ''; output.appendChild(createDownloadLink(blob, 'recording.webm', 'Download Recording')); }; mediaRecorder.start(); recordBtn.querySelector('span').textContent = 'Stop Recording'; recordBtn.style.borderColor = 'red'; } else { mediaRecorder.stop(); recordBtn.querySelector('span').textContent = 'Start Recording'; recordBtn.style.borderColor = 'var(--primary-accent)'; } }; }).catch(err => { output.textContent = 'Error: Could not access webcam. ' + err.message; }); } }, audioConverter: { title: 'Audio Recorder', getUI: () => `

    Record audio from your microphone.

    `, init: () => { const recordBtn = document.getElementById('audio-record-btn'); const output = document.getElementById('audio-output'); let mediaRecorder, chunks = []; navigator.mediaDevices.getUserMedia({ audio: true }) .then(stream => { recordBtn.onclick = () => { if (!mediaRecorder || mediaRecorder.state === 'inactive') { mediaRecorder = new MediaRecorder(stream); mediaRecorder.ondataavailable = e => chunks.push(e.data); mediaRecorder.onstop = () => { const blob = new Blob(chunks, { type: 'audio/webm' }); chunks = []; output.innerHTML = ''; output.appendChild(createDownloadLink(blob, 'recording.webm', 'Download Audio')); }; mediaRecorder.start(); recordBtn.querySelector('span').textContent = 'Stop Recording'; recordBtn.style.borderColor = 'red'; } else { mediaRecorder.stop(); recordBtn.querySelector('span').textContent = 'Start Recording'; recordBtn.style.borderColor = 'var(--primary-accent)'; } }; }).catch(err => { output.textContent = 'Error: Could not access microphone. ' + err.message; }); } }, audioTrimmer: { title: 'Audio Trimmer', getUI: () => `
    `, init: () => { const upload = document.getElementById('audio-upload'); const controls = document.getElementById('trim-controls'); const trimBtn = document.getElementById('trim-btn'); const output = document.getElementById('trim-output'); let audioBuffer; const audioContext = new (window.AudioContext || window.webkitAudioContext)(); upload.addEventListener('change', e => { const file = e.target.files[0]; if (!file) return; output.textContent = 'Loading audio...'; const reader = new FileReader(); reader.onload = res => { audioContext.decodeAudioData(res.target.result, buffer => { audioBuffer = buffer; document.getElementById('end-time').value = buffer.duration.toFixed(1); controls.style.display = 'block'; output.textContent = `Audio loaded. Duration: ${buffer.duration.toFixed(2)}s`; }, err => output.textContent = 'Error decoding audio file.'); }; reader.readAsArrayBuffer(file); }); trimBtn.addEventListener('click', () => { if (!audioBuffer) return; const startTime = parseFloat(document.getElementById('start-time').value); const endTime = parseFloat(document.getElementById('end-time').value); if (startTime >= endTime || endTime > audioBuffer.duration) { alert('Invalid start or end time.'); return; } const startOffset = Math.floor(startTime * audioBuffer.sampleRate); const endOffset = Math.floor(endTime * audioBuffer.sampleRate); const frameCount = endOffset - startOffset; const newBuffer = audioContext.createBuffer( audioBuffer.numberOfChannels, frameCount, audioBuffer.sampleRate ); for (let i = 0; i < audioBuffer.numberOfChannels; i++) { const channelData = audioBuffer.getChannelData(i); const newChannelData = newBuffer.getChannelData(i); newChannelData.set(channelData.subarray(startOffset, endOffset)); } const wavBlob = bufferToWave(newBuffer, frameCount); output.innerHTML = ''; output.appendChild(createDownloadLink(wavBlob, 'trimmed-audio.wav', 'Download Trimmed WAV')); }); function bufferToWave(abuffer, len) { let numOfChan = abuffer.numberOfChannels, length = len * numOfChan * 2 + 44, buffer = new ArrayBuffer(length), view = new DataView(buffer), channels = [], i, sample, offset = 0, pos = 0; setUint32(0x46464952); setUint32(length - 8); setUint32(0x45564157); setUint32(0x20746d66); setUint32(16); setUint16(1); setUint16(numOfChan); setUint32(abuffer.sampleRate); setUint32(abuffer.sampleRate * 2 * numOfChan); setUint16(numOfChan * 2); setUint16(16); setUint32(0x61746164); setUint32(length - pos - 4); for (i = 0; i < abuffer.numberOfChannels; i++) channels.push(abuffer.getChannelData(i)); while (pos < length) { for (i = 0; i < numOfChan; i++) { sample = Math.max(-1, Math.min(1, channels[i][offset])); sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767) | 0; view.setInt16(pos, sample, true); pos += 2; } offset++; } return new Blob([view], { type: 'audio/wav' }); function setUint16(data) { view.setUint16(pos, data, true); pos += 2; } function setUint32(data) { view.setUint32(pos, data, true); pos += 4; } } } }, ageCalculator: { title: 'Age Calculator', getUI: () => `
    `, init: () => { document.getElementById('calc-age-btn').addEventListener('click', () => { const dobString = document.getElementById('dob-input').value; const resultDiv = document.getElementById('age-result'); if (!dobString) { resultDiv.textContent = 'Please select a date.'; return; } const dob = new Date(dobString); const today = new Date(); let age = today.getFullYear() - dob.getFullYear(); const m = today.getMonth() - dob.getMonth(); if (m < 0 || (m === 0 && today.getDate() < dob.getDate())) { age--; } resultDiv.textContent = `You are ${age} years old.`; }); } }, emiCalculator: { title: 'EMI Calculator', getUI: () => `
    `, init: () => { document.getElementById('calc-emi-btn').addEventListener('click', () => { const p = parseFloat(document.getElementById('principal').value); const r = parseFloat(document.getElementById('interest').value) / 12 / 100; const n = parseFloat(document.getElementById('tenure').value) * 12; const resultDiv = document.getElementById('emi-result'); if (isNaN(p) || isNaN(r) || isNaN(n) || p <= 0 || r <= 0 || n <= 0) { resultDiv.textContent = 'Please enter valid positive numbers in all fields.'; return; } const emi = p * r * (Math.pow(1 + r, n)) / (Math.pow(1 + r, n) - 1); const totalPayable = emi * n; const totalInterest = totalPayable - p; resultDiv.innerHTML = `

    Monthly EMI: $${emi.toFixed(2)}

    Total Interest Payable: $${totalInterest.toFixed(2)}

    Total Payment (Principal + Interest): $${totalPayable.toFixed(2)}

    `; }); } }, sipCalculator: { title: 'SIP Calculator', getUI: () => `
    `, init: () => { document.getElementById('calc-sip-btn').addEventListener('click', () => { const i = parseFloat(document.getElementById('monthly-investment').value); const r = parseFloat(document.getElementById('expected-return').value) / 100 / 12; const n = parseFloat(document.getElementById('investment-period').value) * 12; const resultDiv = document.getElementById('sip-result'); if (isNaN(i) || isNaN(r) || isNaN(n) || i <= 0 || r < 0 || n <= 0) { resultDiv.textContent = 'Please enter valid positive numbers.'; return; } const futureValue = i * ((Math.pow(1 + r, n) - 1) / r) * (1 + r); const investedAmount = i * n; const wealthGained = futureValue - investedAmount; resultDiv.innerHTML = `

    Invested Amount: $${investedAmount.toFixed(2)}

    Est. Returns: $${wealthGained.toFixed(2)}

    Total Value: $${futureValue.toFixed(2)}

    `; }); } }, qrCodeGenerator: { title: 'QR Code Generator', getUI: () => `
    Your QR Code will appear here
    `, init: () => { const textInput = document.getElementById('qr-text'); const qrContainer = document.getElementById('qr-code-container'); let qrcode = null; const generateQR = () => { const text = textInput.value.trim(); qrContainer.innerHTML = ''; if (text) { if (!qrcode) { qrcode = new QRCode(qrContainer, { text: text, width: 256, height: 256, colorDark: "#000000", colorLight: "#ffffff", correctLevel: QRCode.CorrectLevel.H }); } else { qrcode.makeCode(text); } } else { qrContainer.innerHTML = 'Your QR Code will appear here'; } }; textInput.addEventListener('input', generateQR); generateQR(); } }, passwordGenerator: { title: 'Password Generator', getUI: () => `
    16
    `, init: () => { const lengthSlider = document.getElementById('pass-length'); const lengthVal = document.getElementById('length-val'); const genBtn = document.getElementById('gen-pass-btn'); const outputDiv = document.getElementById('pass-output'); const inclUpper = document.getElementById('incl-upper'); const inclLower = document.getElementById('incl-lower'); const inclNums = document.getElementById('incl-nums'); const inclSyms = document.getElementById('incl-syms'); const chars = { upper: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', lower: 'abcdefghijklmnopqrstuvwxyz', nums: '0123456789', syms: '!@#$%^&*()_+~`|}{[]:;?><,./-=' }; lengthSlider.addEventListener('input', () => lengthVal.textContent = lengthSlider.value); const generatePassword = () => { const length = parseInt(lengthSlider.value); let charset = ''; if (inclUpper.checked) charset += chars.upper; if (inclLower.checked) charset += chars.lower; if (inclNums.checked) charset += chars.nums; if (inclSyms.checked) charset += chars.syms; if (charset === '') { outputDiv.textContent = 'Please select at least one character set.'; return; } let password = ''; for (let i = 0; i < length; i++) { password += charset.charAt(Math.floor(Math.random() * charset.length)); } outputDiv.textContent = password; }; genBtn.addEventListener('click', generatePassword); outputDiv.addEventListener('click', () => { if (outputDiv.textContent) { navigator.clipboard.writeText(outputDiv.textContent) .then(() => alert('Password copied to clipboard!')) .catch(err => alert('Failed to copy password.')); } }); generatePassword(); } }, loremIpsumGenerator: { title: 'Lorem Ipsum Generator', getUI: () => `
    Generated text will appear here.
    `, init: () => { const generateBtn = document.getElementById('lorem-generate'); const outputDiv = document.getElementById('lorem-output'); const loremText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Phasellus egestas tellus rutrum tellus pellentesque eu. Mattis enim ut tellus elementum sagittis vitae et. Congue nisi vitae suscipit tellus mauris a diam. Ac turpis egestas integer eget aliquet nibh praesent. Nisl tincidunt eget nullam non."; const generateLorem = () => { const count = parseInt(document.getElementById('lorem-count').value, 10); let result = ''; if (count > 0) { for (let i = 0; i < count; i++) { result += `

    ${loremText}

    `; } outputDiv.innerHTML = result; } else { outputDiv.innerHTML = 'Please enter a valid number of paragraphs.'; } }; generateBtn.addEventListener('click', generateLorem); generateLorem(); } }, wordCounter: { title: 'Word, Character & Sentence Counter', getUI: () => `

    Words: 0

    Characters: 0

    Sentences: 0

    `, init: () => { document.getElementById('text-input').addEventListener('input', e => { const text = e.target.value; const words = text.trim() === '' ? 0 : text.trim().split(/\s+/).length; const chars = text.length; const sentences = (text.match(/[.!?…]+/g) || []).length; document.getElementById('count-result').innerHTML = `

    Words: ${words}

    Characters: ${chars}

    Sentences: ${sentences}

    `; }); } }, base64EncoderDecoder: { title: 'Base64 Encoder/Decoder', getUI: () => `
    `, init: () => { const input = document.getElementById('base64-input'); const output = document.getElementById('base64-output'); document.getElementById('encode-btn').addEventListener('click', () => { try { output.textContent = btoa(unescape(encodeURIComponent(input.value))); } catch (e) { output.textContent = 'Error: ' + e.message; } }); document.getElementById('decode-btn').addEventListener('click', () => { try { output.textContent = decodeURIComponent(escape(atob(input.value))); } catch (e) { output.textContent = 'Error: Invalid Base64 string.'; } }); } }, caseConverter: { title: 'Case Converter', getUI: () => `
    `, init: () => { const textarea = document.getElementById('case-input'); document.getElementById('case-upper').addEventListener('click', () => { textarea.value = textarea.value.toUpperCase(); }); document.getElementById('case-lower').addEventListener('click', () => { textarea.value = textarea.value.toLowerCase(); }); document.getElementById('case-title').addEventListener('click', () => { textarea.value = textarea.value.toLowerCase().replace(/\b\w/g, char => char.toUpperCase()); }); document.getElementById('case-sentence').addEventListener('click', () => { textarea.value = textarea.value.toLowerCase().replace(/(^\w{1}|\.\s*\w{1})/g, char => char.toUpperCase()); }); } }, colorPicker: { title: 'Color Picker', getUI: () => `
    `, init: () => { const colorInput = document.getElementById('color-input'); const output = document.getElementById('color-output'); const updateColorValues = (hex) => { const rgb = hexToRgb(hex); const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b); output.innerHTML = `

    HEX: ${hex}

    RGB: rgb(${rgb.r}, ${rgb.g}, ${rgb.b})

    HSL: hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)

    `; output.style.borderColor = hex; }; colorInput.addEventListener('input', e => updateColorValues(e.target.value)); const initialColor = document.body.classList.contains('light-mode') ? '#007bff' : '#42f8f5'; colorInput.value = initialColor; updateColorValues(initialColor); function hexToRgb(hex) { let r = 0, g = 0, b = 0; if (hex.length == 4) { r = "0x" + hex[1] + hex[1]; g = "0x" + hex[2] + hex[2]; b = "0x" + hex[3] + hex[3]; } else if (hex.length == 7) { r = "0x" + hex[1] + hex[2]; g = "0x" + hex[3] + hex[4]; b = "0x" + hex[5] + hex[6]; } return { r: +r, g: +g, b: +b }; } function rgbToHsl(r, g, b) { r /= 255; g /= 255; b /= 255; let max = Math.max(r, g, b), min = Math.min(r, g, b); let h=0, s, l = (max + min) / 2; if (max != min) { let 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; } else { h = s = 0; } return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) }; } } }, textToSpeech: { title: 'Text to Speech', getUI: () => `
    `, init: () => { const speakBtn = document.getElementById('speak-btn'); const textInput = document.getElementById('tts-text'); speakBtn.addEventListener('click', () => { const text = textInput.value; if (speechSynthesis.speaking) { speechSynthesis.cancel(); } if (text !== '') { const utterance = new SpeechSynthesisUtterance(text); speechSynthesis.speak(utterance); } }); } }, speechToText: { title: 'Speech to Text', getUI: () => `

    Click the button and start speaking.

    Transcript will appear here...
    `, init: () => { const sttBtn = document.getElementById('stt-btn'); const output = document.getElementById('stt-output'); const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; if (!SpeechRecognition) { output.innerHTML = 'Speech recognition is not supported in your browser.'; sttBtn.disabled = true; return; } const recognition = new SpeechRecognition(); recognition.interimResults = true; recognition.lang = 'en-US'; let listening = false; recognition.onresult = event => { let transcript = Array.from(event.results).map(result => result[0]).map(result => result.transcript).join(''); output.textContent = transcript; }; recognition.onend = () => { sttBtn.querySelector('span').textContent = 'Start Listening'; listening = false; }; sttBtn.addEventListener('click', () => { if (listening) { recognition.stop(); } else { recognition.start(); sttBtn.querySelector('span').textContent = 'Stop Listening'; } listening = !listening; }); } }, jsonFormatter: { title: 'JSON Formatter & Validator', getUI: () => `
    `, init: () => { document.getElementById('format-json-btn').addEventListener('click', () => { const input = document.getElementById('json-input').value; const output = document.getElementById('json-output'); try { const parsed = JSON.parse(input); const formatted = JSON.stringify(parsed, null, 4); output.innerHTML = `
    ${formatted}
    `; output.style.borderColor = 'var(--primary-accent)'; } catch (e) { output.textContent = 'Invalid JSON: ' + e.message; output.style.borderColor = 'red'; } }); } }, urlEncoderDecoder: { title: 'URL Encoder / Decoder', getUI: () => `
    `, init: () => { const input = document.getElementById('url-input'); const output = document.getElementById('url-output'); document.getElementById('url-encode-btn').addEventListener('click', () => { try { output.textContent = encodeURIComponent(input.value); } catch (e) { output.textContent = 'Error: ' + e.message; } }); document.getElementById('url-decode-btn').addEventListener('click', () => { try { output.textContent = decodeURIComponent(input.value); } catch (e) { output.textContent = 'Error: Invalid URL component string.'; } }); } }, unitConverter: { title: 'Unit Converter', getUI: () => `
    =
    `, init: () => { const categorySelect = document.getElementById('unit-category'); const fromSelect = document.getElementById('from-unit'); const toSelect = document.getElementById('to-unit'); const input = document.getElementById('unit-input'); const output = document.getElementById('unit-output'); const units = { length: { 'meters': 1, 'kilometers': 1000, 'miles': 1609.34, 'feet': 0.3048 }, weight: { 'grams': 1, 'kilograms': 1000, 'pounds': 453.592, 'ounces': 28.3495 }, temperature: { 'celsius': 'c', 'fahrenheit': 'f', 'kelvin': 'k' } }; function populateUnits() { const category = categorySelect.value; fromSelect.innerHTML = ''; toSelect.innerHTML = ''; Object.keys(units[category]).forEach(unit => { fromSelect.add(new Option(unit, unit)); toSelect.add(new Option(unit, unit)); }); if (category === 'length') { fromSelect.value = 'meters'; toSelect.value = 'kilometers'; } else if (category === 'weight') { fromSelect.value = 'kilograms'; toSelect.value = 'grams'; } else { fromSelect.value = 'celsius'; toSelect.value = 'fahrenheit'; } convert(); } function convert() { const category = categorySelect.value; const fromUnit = fromSelect.value; const toUnit = toSelect.value; const val = parseFloat(input.value); if (isNaN(val)) return; let result; if (category !== 'temperature') { const baseValue = val * units[category][fromUnit]; result = baseValue / units[category][toUnit]; } else { if (fromUnit === toUnit) { result = val; } else if (fromUnit === 'celsius') { result = toUnit === 'fahrenheit' ? (val * 9/5) + 32 : val + 273.15; } else if (fromUnit === 'fahrenheit') { result = toUnit === 'celsius' ? (val - 32) * 5/9 : ((val - 32) * 5/9) + 273.15; } else { result = toUnit === 'celsius' ? val - 273.15 : ((val - 273.15) * 9/5) + 32; } } output.value = result.toFixed(3); } categorySelect.addEventListener('change', populateUnits); [fromSelect, toSelect, input].forEach(el => el.addEventListener('change', convert)); input.addEventListener('input', convert); populateUnits(); } }, bmiCalculator: { title: 'BMI Calculator', getUI: () => `
    `, init: () => { document.getElementById('calc-bmi-btn').addEventListener('click', () => { const height = parseFloat(document.getElementById('height').value) / 100; const weight = parseFloat(document.getElementById('weight').value); const resultDiv = document.getElementById('bmi-result'); if (isNaN(height) || isNaN(weight) || height <= 0 || weight <= 0) { resultDiv.textContent = 'Please enter valid height and weight.'; return; } const bmi = weight / (height * height); 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 = `Your BMI is ${bmi.toFixed(2)}.
    This is considered: ${category}.`; }); } }, timerStopwatch: { title: 'Timer / Stopwatch', getUI: () => `

    Stopwatch

    00:00:00.000
    `, init: () => { const stopwatchView = document.getElementById('stopwatch-view'); const timerView = document.getElementById('timer-view'); document.getElementById('show-stopwatch').addEventListener('click', () => { stopwatchView.style.display = 'block'; timerView.style.display = 'none'; }); document.getElementById('show-timer').addEventListener('click', () => { stopwatchView.style.display = 'none'; timerView.style.display = 'block'; }); let swInterval, swStartTime, swElapsedTime = 0; const swDisplay = document.getElementById('stopwatch-display'); function formatTime(ms) { const d = new Date(ms); return `${d.getUTCMinutes().toString().padStart(2, '0')}:${d.getUTCSeconds().toString().padStart(2, '0')}.${d.getUTCMilliseconds().toString().padStart(3, '0')}`; } document.getElementById('sw-start').addEventListener('click', () => { if (swInterval) return; swStartTime = Date.now() - swElapsedTime; swInterval = setInterval(() => { swElapsedTime = Date.now() - swStartTime; swDisplay.textContent = formatTime(swElapsedTime); }, 10); }); document.getElementById('sw-stop').addEventListener('click', () => { clearInterval(swInterval); swInterval = null; }); document.getElementById('sw-reset').addEventListener('click', () => { clearInterval(swInterval); swInterval = null; swElapsedTime = 0; swDisplay.textContent = '00:00:00.000'; }); let timerInterval, totalSeconds; const h_in=document.getElementById('timer-h'), m_in=document.getElementById('timer-m'), s_in=document.getElementById('timer-s'); function startTimer() { if (timerInterval) return; timerInterval = setInterval(() => { if (totalSeconds <= 0) { clearInterval(timerInterval); timerInterval = null; alert('Timer finished!'); updateTimerDisplay(); return; } totalSeconds--; updateTimerDisplay(); }, 1000); } function updateTimerDisplay() { h_in.value = Math.floor(totalSeconds / 3600); m_in.value = Math.floor((totalSeconds % 3600) / 60); s_in.value = totalSeconds % 60; } document.getElementById('timer-start').addEventListener('click', () => { if(!timerInterval) { totalSeconds = (parseInt(h_in.value)*3600) + (parseInt(m_in.value)*60) + parseInt(s_in.value); } startTimer(); }); document.getElementById('timer-pause').addEventListener('click', () => { clearInterval(timerInterval); timerInterval = null; }); document.getElementById('timer-reset').addEventListener('click', () => { clearInterval(timerInterval); timerInterval = null; h_in.value=0; m_in.value=1; s_in.value=30; totalSeconds = 90; updateTimerDisplay(); }); } }, about: { title: 'About Onic Computer', getUI: () => `

    Onic Computer – Multi Tools Hub is a demonstration of what's possible with modern, vanilla web technologies.


    This entire website is built using only HTML, CSS, and JavaScript. There are no external frameworks like React or Vue, and no UI libraries like Bootstrap, with only minimal, targeted libraries for complex tasks like PDF manipulation.


    Key Features:

    • Pure Frontend: All 35+ tools run directly in your browser.
    • Light/Dark Mode: A beautiful, persistent theme toggle for user comfort.
    • High Performance: No heavy frameworks means a faster, more responsive experience.
    • Modern UI/UX: A futuristic, glowing design built with CSS variables, transitions, and shadows.
    • Responsive Design: Fully functional on desktop, tablet, and mobile devices.

    This project was created to showcase proficiency in core web fundamentals and the power of native browser APIs like Web Audio, Web Speech, and Canvas.

    `, init:() => {} }, }; });
    Share This: