<html lang="en">
<head>
<meta charset="UTF-8"></meta>
<meta content="width=device-width, initial-scale=1.0" name="viewport"></meta>
<title>Digital Tools Hub - 15 Essential Web Tools</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"></link>
<link href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.4.0/css/all.min.css" rel="stylesheet"></link>
<style>
html {
scroll-behavior: smooth;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.tool-container {
min-height: 350px;
}
.category-section {
break-inside: avoid;
}
.tool-card {
transition: all 0.3s ease;
break-inside: avoid;
}
.tool-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
}
.nav-item {
transition: all 0.3s ease;
}
.nav-item:hover {
background-color: #f3f4f6;
border-radius: 0.375rem;
}
.color-swatch {
width: 30px;
height: 30px;
display: inline-block;
margin: 5px;
cursor: pointer;
}
/* Hide scrollbar for chrome */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
}
::-webkit-scrollbar-thumb {
background: #888;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* Custom input slider styles */
input[type=range] {
-webkit-appearance: none;
width: 100%;
margin: 8px 0;
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 8px;
cursor: pointer;
background: #e5e7eb;
border-radius: 4px;
}
input[type=range]::-webkit-slider-thumb {
height: 20px;
width: 20px;
border-radius: 10px;
background: #3b82f6;
cursor: pointer;
-webkit-appearance: none;
margin-top: -6px;
}
input[type=range]:focus {
outline: none;
}
input[type=range]:focus::-webkit-slider-runnable-track {
background: #dbeafe;
}
canvas {
max-width: 100%;
}
#qr-code-result {
max-width: 100%;
}
@media print {
.no-print {
display: none;
}
body {
width: 100%;
margin: 0;
padding: 0;
}
}
</style>
</head>
<body class="bg-gray-100">
<div class="container mx-auto px-4 py-8">
<!-- Header -->
<header class="bg-white shadow-md rounded-lg p-6 mb-8">
<h1 class="text-3xl font-bold text-gray-800">Digital Tools Hub</h1>
<p class="text-gray-600 mt-2">15 Essential Web Tools for Everyday Tasks</p>
<!-- Navigation -->
<nav class="mt-6 no-print">
<ul class="flex flex-wrap gap-2">
<li><a class="nav-item px-4 py-2 text-blue-600 hover:text-blue-800 font-medium" href="#text-tools">Text Tools</a></li>
<li><a class="nav-item px-4 py-2 text-blue-600 hover:text-blue-800 font-medium" href="#converters">Converters</a></li>
<li><a class="nav-item px-4 py-2 text-blue-600 hover:text-blue-800 font-medium" href="#calculators">Calculators</a></li>
<li><a class="nav-item px-4 py-2 text-blue-600 hover:text-blue-800 font-medium" href="#generators">Generators</a></li>
<li><a class="nav-item px-4 py-2 text-blue-600 hover:text-blue-800 font-medium" href="#utilities">Utilities</a></li>
</ul>
</nav>
</header>
<!-- Text Tools Section -->
<section class="category-section mb-12" id="text-tools">
<h2 class="text-2xl font-bold text-gray-800 mb-6 pb-2 border-b-2 border-blue-500">Text Tools</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- Tool 1: Text Case Converter -->
<div class="tool-card bg-white rounded-lg shadow-md p-6">
<h3 class="text-xl font-bold text-gray-800 mb-3">1. Text Case Converter</h3>
<div class="tool-container">
<textarea class="w-full h-32 p-2 border rounded mb-3" id="case-converter-input" placeholder="Enter text to convert..."></textarea>
<div class="grid grid-cols-2 gap-2 mb-3">
<button class="bg-blue-500 text-white py-2 px-3 rounded hover:bg-blue-600" id="uppercase-btn">UPPERCASE</button>
<button class="bg-blue-500 text-white py-2 px-3 rounded hover:bg-blue-600" id="lowercase-btn">lowercase</button>
<button class="bg-blue-500 text-white py-2 px-3 rounded hover:bg-blue-600" id="capitalize-btn">Capitalize Each Word</button>
<button class="bg-blue-500 text-white py-2 px-3 rounded hover:bg-blue-600" id="sentence-case-btn">Sentence case</button>
</div>
<div class="text-right">
<button class="bg-gray-200 text-gray-700 py-1 px-3 rounded hover:bg-gray-300" id="copy-case-converter">
<i class="fas fa-copy mr-1"></i> Copy
</button>
</div>
</div>
</div>
<!-- Tool 2: Character/Word Counter -->
<div class="tool-card bg-white rounded-lg shadow-md p-6">
<h3 class="text-xl font-bold text-gray-800 mb-3">2. Character/Word Counter</h3>
<div class="tool-container">
<textarea class="w-full h-40 p-2 border rounded mb-3" id="text-counter-input" placeholder="Type or paste text here to count..."></textarea>
<div class="grid grid-cols-2 gap-4">
<div class="bg-blue-100 p-3 rounded text-center">
<p class="text-sm text-gray-700">Characters:</p>
<p class="text-2xl font-bold text-blue-700" id="char-count">0</p>
</div>
<div class="bg-green-100 p-3 rounded text-center">
<p class="text-sm text-gray-700">Words:</p>
<p class="text-2xl font-bold text-green-700" id="word-count">0</p>
</div>
</div>
</div>
</div>
<!-- Tool 3: Find and Replace -->
<div class="tool-card bg-white rounded-lg shadow-md p-6">
<h3 class="text-xl font-bold text-gray-800 mb-3">3. Find and Replace</h3>
<div class="tool-container">
<textarea class="w-full h-32 p-2 border rounded mb-3" id="find-replace-text" placeholder="Enter your text here..."></textarea>
<div class="grid grid-cols-2 gap-2 mb-3">
<div>
<label class="block text-gray-700 mb-1 text-sm">Find:</label>
<input class="w-full p-2 border rounded" id="find-text" type="text" />
</div>
<div>
<label class="block text-gray-700 mb-1 text-sm">Replace with:</label>
<input class="w-full p-2 border rounded" id="replace-text" type="text" />
</div>
</div>
<div class="mb-2">
<label class="inline-flex items-center">
<input class="form-checkbox" id="case-sensitive" type="checkbox" />
<span class="ml-2 text-sm text-gray-700">Case sensitive</span>
</label>
</div>
<button class="bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 mb-3 w-full" id="replace-button">Replace All</button>
</div>
</div>
</div>
</section>
<!-- Converters Section -->
<section class="category-section mb-12" id="converters">
<h2 class="text-2xl font-bold text-gray-800 mb-6 pb-2 border-b-2 border-blue-500">Converters</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- Tool 4: Length Converter -->
<div class="tool-card bg-white rounded-lg shadow-md p-6">
<h3 class="text-xl font-bold text-gray-800 mb-3">4. Length Converter</h3>
<div class="tool-container">
<div class="mb-3">
<label class="block text-gray-700 mb-1">Value:</label>
<input class="w-full p-2 border rounded" id="length-value" type="number" value="1" />
</div>
<div class="grid grid-cols-2 gap-3 mb-4">
<div>
<label class="block text-gray-700 mb-1">From:</label>
<select class="w-full p-2 border rounded" id="length-from">
<option value="mm">Millimeters (mm)</option>
<option value="cm">Centimeters (cm)</option>
<option selected="" value="m">Meters (m)</option>
<option value="km">Kilometers (km)</option>
<option value="in">Inches (in)</option>
<option value="ft">Feet (ft)</option>
<option value="yd">Yards (yd)</option>
<option value="mi">Miles (mi)</option>
</select>
</div>
<div>
<label class="block text-gray-700 mb-1">To:</label>
<select class="w-full p-2 border rounded" id="length-to">
<option value="mm">Millimeters (mm)</option>
<option value="cm">Centimeters (cm)</option>
<option value="m">Meters (m)</option>
<option value="km">Kilometers (km)</option>
<option value="in">Inches (in)</option>
<option selected="" value="ft">Feet (ft)</option>
<option value="yd">Yards (yd)</option>
<option value="mi">Miles (mi)</option>
</select>
</div>
</div>
<button class="bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 mb-3 w-full" id="convert-length">Convert</button>
<div class="bg-gray-100 p-3 rounded">
<p class="text-sm text-gray-700">Result:</p>
<p class="text-xl font-bold text-blue-700" id="length-result">3.28084 ft</p>
</div>
</div>
</div>
<!-- Tool 5: Temperature Converter -->
<div class="tool-card bg-white rounded-lg shadow-md p-6">
<h3 class="text-xl font-bold text-gray-800 mb-3">5. Temperature Converter</h3>
<div class="tool-container">
<div class="grid grid-cols-1 gap-4 mb-4">
<div>
<label class="block text-gray-700 mb-1">Celsius (°C):</label>
<input class="w-full p-2 border rounded" id="celsius" type="number" value="0" />
</div>
<div>
<label class="block text-gray-700 mb-1">Fahrenheit (°F):</label>
<input class="w-full p-2 border rounded" id="fahrenheit" type="number" value="32" />
</div>
<div>
<label class="block text-gray-700 mb-1">Kelvin (K):</label>
<input class="w-full p-2 border rounded" id="kelvin" type="number" value="273.15" />
</div>
</div>
<div class="bg-blue-100 p-3 rounded text-center">
<p class="text-sm text-gray-700">Quick Info:</p>
<p class="text-sm">Water freezes at 0°C / 32°F / 273.15K</p>
<p class="text-sm">Water boils at 100°C / 212°F / 373.15K</p>
</div>
</div>
</div>
<!-- Tool 6: Base64 Encoder/Decoder -->
<div class="tool-card bg-white rounded-lg shadow-md p-6">
<h3 class="text-xl font-bold text-gray-800 mb-3">6. Base64 Encoder/Decoder</h3>
<div class="tool-container">
<div class="mb-3">
<label class="block text-gray-700 mb-1">Input:</label>
<textarea class="w-full h-32 p-2 border rounded" id="base64-input" placeholder="Enter text to encode or base64 to decode..."></textarea>
</div>
<div class="flex gap-2 mb-3">
<button class="flex-1 bg-blue-500 text-white py-2 px-3 rounded hover:bg-blue-600" id="encode-base64">Encode</button>
<button class="flex-1 bg-green-500 text-white py-2 px-3 rounded hover:bg-green-600" id="decode-base64">Decode</button>
</div>
<div class="mb-3">
<label class="block text-gray-700 mb-1">Result:</label>
<textarea class="w-full h-32 p-2 border rounded bg-gray-50" id="base64-output" readonly=""></textarea>
</div>
<div class="text-right">
<button class="bg-gray-200 text-gray-700 py-1 px-3 rounded hover:bg-gray-300" id="copy-base64">
<i class="fas fa-copy mr-1"></i> Copy Result
</button>
</div>
</div>
</div>
</div>
</section>
<!-- Calculators Section -->
<section class="category-section mb-12" id="calculators">
<h2 class="text-2xl font-bold text-gray-800 mb-6 pb-2 border-b-2 border-blue-500">Calculators</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- Tool 7: Basic Calculator -->
<div class="tool-card bg-white rounded-lg shadow-md p-6">
<h3 class="text-xl font-bold text-gray-800 mb-3">7. Basic Calculator</h3>
<div class="tool-container">
<div class="bg-gray-100 p-3 rounded mb-4">
<input class="w-full p-2 text-right text-xl font-bold" id="calc-display" readonly="" type="text" value="0" />
</div>
<div class="grid grid-cols-4 gap-2">
<button class="calc-btn bg-gray-200 text-gray-800 py-2 rounded hover:bg-gray-300" data-value="7">7</button>
<button class="calc-btn bg-gray-200 text-gray-800 py-2 rounded hover:bg-gray-300" data-value="8">8</button>
<button class="calc-btn bg-gray-200 text-gray-800 py-2 rounded hover:bg-gray-300" data-value="9">9</button>
<button class="calc-btn bg-yellow-500 text-white py-2 rounded hover:bg-yellow-600" data-value="/">/</button>
<button class="calc-btn bg-gray-200 text-gray-800 py-2 rounded hover:bg-gray-300" data-value="4">4</button>
<button class="calc-btn bg-gray-200 text-gray-800 py-2 rounded hover:bg-gray-300" data-value="5">5</button>
<button class="calc-btn bg-gray-200 text-gray-800 py-2 rounded hover:bg-gray-300" data-value="6">6</button>
<button class="calc-btn bg-yellow-500 text-white py-2 rounded hover:bg-yellow-600" data-value="*">×</button>
<button class="calc-btn bg-gray-200 text-gray-800 py-2 rounded hover:bg-gray-300" data-value="1">1</button>
<button class="calc-btn bg-gray-200 text-gray-800 py-2 rounded hover:bg-gray-300" data-value="2">2</button>
<button class="calc-btn bg-gray-200 text-gray-800 py-2 rounded hover:bg-gray-300" data-value="3">3</button>
<button class="calc-btn bg-yellow-500 text-white py-2 rounded hover:bg-yellow-600" data-value="-">-</button>
<button class="calc-btn bg-gray-200 text-gray-800 py-2 rounded hover:bg-gray-300" data-value="0">0</button>
<button class="calc-btn bg-gray-200 text-gray-800 py-2 rounded hover:bg-gray-300" data-value=".">.</button>
<button class="bg-blue-500 text-white py-2 rounded hover:bg-blue-600" data-value="=" id="calc-equals">=</button>
<button class="calc-btn bg-yellow-500 text-white py-2 rounded hover:bg-yellow-600" data-value="+">+</button>
<button class="bg-red-500 text-white py-2 rounded hover:bg-red-600 col-span-2" id="calc-clear">Clear</button>
<button class="bg-orange-500 text-white py-2 rounded hover:bg-orange-600 col-span-2" id="calc-backspace">⌫</button>
</div>
</div>
</div>
<!-- Tool 8: Percentage Calculator -->
<div class="tool-card bg-white rounded-lg shadow-md p-6">
<h3 class="text-xl font-bold text-gray-800 mb-3">8. Percentage Calculator</h3>
<div class="tool-container">
<div class="bg-gray-100 p-3 rounded mb-4">
<h4 class="font-bold mb-2">What is X% of Y?</h4>
<div class="grid grid-cols-5 gap-1 items-center mb-3">
<div class="col-span-1">
<input class="w-full p-2 border rounded" id="percent-x" type="number" value="15" />
</div>
<div class="col-span-1 text-center">% of</div>
<div class="col-span-1">
<input class="w-full p-2 border rounded" id="percent-y" type="number" value="100" />
</div>
<div class="col-span-1 text-center">=</div>
<div class="col-span-1">
<input class="w-full p-2 border rounded bg-blue-50" id="percent-result1" readonly="" type="text" value="15" />
</div>
</div>
<h4 class="font-bold mb-2">X is what % of Y?</h4>
<div class="grid grid-cols-5 gap-1 items-center mb-3">
<div class="col-span-1">
<input class="w-full p-2 border rounded" id="x-value" type="number" value="15" />
</div>
<div class="col-span-1 text-center">is what % of</div>
<div class="col-span-1">
<input class="w-full p-2 border rounded" id="y-value" type="number" value="100" />
</div>
<div class="col-span-1 text-center">=</div>
<div class="col-span-1">
<input class="w-full p-2 border rounded bg-blue-50" id="percent-result2" readonly="" type="text" value="15%" />
</div>
</div>
<h4 class="font-bold mb-2">X is Y% of what?</h4>
<div class="grid grid-cols-5 gap-1 items-center">
<div class="col-span-1">
<input class="w-full p-2 border rounded" id="result-x" type="number" value="15" />
</div>
<div class="col-span-1 text-center">is</div>
<div class="col-span-1">
<input class="w-full p-2 border rounded" id="percent-of-what" type="number" value="15" />
</div>
<div class="col-span-1 text-center">% of</div>
<div class="col-span-1">
<input class="w-full p-2 border rounded bg-blue-50" id="percent-result3" readonly="" type="text" value="100" />
</div>
</div>
</div>
<button class="bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 w-full" id="calculate-percentage">Calculate All</button>
</div>
</div>
<!-- Tool 9: Tip Calculator -->
<div class="tool-card bg-white rounded-lg shadow-md p-6">
<h3 class="text-xl font-bold text-gray-800 mb-3">9. Tip Calculator</h3>
<div class="tool-container">
<div class="mb-3">
<label class="block text-gray-700 mb-1">Bill Amount:</label>
<div class="relative">
<span class="absolute left-3 top-2">$</span>
<input class="w-full p-2 pl-7 border rounded" id="bill-amount" min="0" step="0.01" type="number" value="50.00" />
</div>
</div>
<div class="mb-3">
<label class="block text-gray-700 mb-1">Tip Percentage:</label>
<div class="flex items-center">
<input class="w-full" id="tip-percentage-slider" max="30" min="0" type="range" value="15" />
<span class="ml-3 w-12 text-center" id="tip-percentage-value">15%</span>
</div>
<div class="flex justify-between mt-1">
<button class="tip-btn px-2 py-1 bg-gray-200 rounded text-sm" data-value="10">10%</button>
<button class="tip-btn px-2 py-1 bg-gray-200 rounded text-sm" data-value="15">15%</button>
<button class="tip-btn px-2 py-1 bg-gray-200 rounded text-sm" data-value="18">18%</button>
<button class="tip-btn px-2 py-1 bg-gray-200 rounded text-sm" data-value="20">20%</button>
<button class="tip-btn px-2 py-1 bg-gray-200 rounded text-sm" data-value="25">25%</button>
</div>
</div>
<div class="mb-3">
<label class="block text-gray-700 mb-1">Number of People:</label>
<input class="w-full p-2 border rounded" id="num-people" min="1" type="number" value="1" />
</div>
<div class="bg-gray-100 p-3 rounded mb-3">
<div class="grid grid-cols-2 gap-2">
<div>
<p class="text-sm text-gray-700">Tip Amount:</p>
<p class="text-xl font-bold text-blue-700" id="tip-amount">$7.50</p>
</div>
<div>
<p class="text-sm text-gray-700">Total:</p>
<p class="text-xl font-bold text-blue-700" id="total-with-tip">$57.50</p>
</div>
<div>
<p class="text-sm text-gray-700">Tip Per Person:</p>
<p class="text-md text-gray-800" id="tip-per-person">$7.50</p>
</div>
<div>
<p class="text-sm text-gray-700">Total Per Person:</p>
<p class="text-md text-gray-800" id="total-per-person">$57.50</p>
</div>
</div>
</div>
<button class="bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 w-full" id="calculate-tip">Recalculate</button>
</div>
</div>
</div>
</section>
<!-- Generators Section -->
<section class="category-section mb-12" id="generators">
<h2 class="text-2xl font-bold text-gray-800 mb-6 pb-2 border-b-2 border-blue-500">Generators</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- Tool 10: Password Generator -->
<div class="tool-card bg-white rounded-lg shadow-md p-6">
<h3 class="text-xl font-bold text-gray-800 mb-3">10. Password Generator</h3>
<div class="tool-container">
<div class="mb-3">
<label class="block text-gray-700 mb-1">Password Length:</label>
<div class="flex items-center">
<input class="w-full" id="password-length" max="32" min="6" type="range" value="12" />
<span class="ml-3 w-8 text-center" id="password-length-display">12</span>
</div>
</div>
<div class="mb-4">
<div class="mb-2">
<label class="inline-flex items-center">
<input checked="" class="form-checkbox" id="include-uppercase" type="checkbox" />
<span class="ml-2">Include Uppercase Letters</span>
</label>
</div>
<div class="mb-2">
<label class="inline-flex items-center">
<input checked="" class="form-checkbox" id="include-lowercase" type="checkbox" />
<span class="ml-2">Include Lowercase Letters</span>
</label>
</div>
<div class="mb-2">
<label class="inline-flex items-center">
<input checked="" class="form-checkbox" id="include-numbers" type="checkbox" />
<span class="ml-2">Include Numbers</span>
</label>
</div>
<div class="mb-2">
<label class="inline-flex items-center">
<input checked="" class="form-checkbox" id="include-symbols" type="checkbox" />
<span class="ml-2">Include Symbols</span>
</label>
</div>
</div>
<button class="bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 mb-3 w-full" id="generate-password">Generate Password</button>
<div class="relative mb-3">
<input class="w-full p-2 pr-10 border rounded font-mono" id="password-output" readonly="" type="text" />
<button class="absolute right-2 top-2 text-gray-500 hover:text-gray-700" id="copy-password">
<i class="fas fa-copy"></i>
</button>
</div>
<div class="bg-blue-50 p-3 rounded text-sm">
<p class="font-bold mb-1">Password Strength:</p>
<div class="w-full bg-gray-300 rounded-full h-2.5">
<div class="bg-green-500 h-2.5 rounded-full" id="password-strength" style="width: 100%;"></div>
</div>
<p class="mt-1 text-gray-700" id="password-strength-text">Strong</p>
</div>
</div>
</div>
<!-- Tool 11: QR Code Generator -->
<div class="tool-card bg-white rounded-lg shadow-md p-6">
<h3 class="text-xl font-bold text-gray-800 mb-3">11. QR Code Generator</h3>
<div class="tool-container">
<div class="mb-3">
<label class="block text-gray-700 mb-1">QR Code Content:</label>
<textarea class="w-full h-24 p-2 border rounded" id="qr-content" placeholder="Enter text, URL, or data for the QR code...">https://example.com</textarea>
</div>
<div class="mb-3">
<label class="block text-gray-700 mb-1">QR Code Size:</label>
<select class="w-full p-2 border rounded" id="qr-size">
<option value="100">Small (100×100)</option>
<option selected="" value="200">Medium (200×200)</option>
<option value="300">Large (300×300)</option>
<option value="400">Extra Large (400×400)</option>
</select>
</div>
<button class="bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 mb-4 w-full" id="generate-qr">Generate QR Code</button>
<div class="flex justify-center mb-3">
<div class="bg-white p-2 border rounded" id="qr-code-result"></div>
</div>
<div class="text-center">
<button class="bg-green-500 text-white py-2 px-4 rounded hover:bg-green-600 disabled:bg-gray-300 disabled:cursor-not-allowed" disabled="" id="download-qr">
<i class="fas fa-download mr-1"></i> Download QR Code
</button>
</div>
</div>
</div>
<!-- Tool 12: Color Palette Generator -->
<div class="tool-card bg-white rounded-lg shadow-md p-6">
<h3 class="text-xl font-bold text-gray-800 mb-3">12. Color Palette Generator</h3>
<div class="tool-container">
<div class="mb-3">
<label class="block text-gray-700 mb-1">Base Color:</label>
<div class="flex">
<input class="w-12 h-10 p-1 border rounded" id="base-color" type="color" value="#4a86e8" />
<input class="flex-1 p-2 border rounded ml-2" id="color-hex" type="text" value="#4a86e8" />
</div>
</div>
<div class="mb-3">
<label class="block text-gray-700 mb-1">Palette Type:</label>
<select class="w-full p-2 border rounded" id="palette-type">
<option selected="" value="analogous">Analogous</option>
<option value="monochromatic">Monochromatic</option>
<option value="triadic">Triadic</option>
<option value="complementary">Complementary</option>
<option value="split">Split Complementary</option>
<option value="random">Random</option>
</select>
</div>
<div class="mb-3">
<label class="block text-gray-700 mb-1">Number of Colors:</label>
<input class="w-full" id="color-count" max="8" min="3" type="range" value="5" />
<div class="flex justify-between text-xs text-gray-500">
<span>3</span>
<span>8</span>
</div>
</div>
<button class="bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 mb-4 w-full" id="generate-palette">Generate Palette</button>
<div class="mb-3">
<div class="flex h-16 rounded overflow-hidden" id="palette-result"></div>
</div>
<div class="grid grid-cols-5 gap-1" id="color-codes"></div>
</div>
</div>
</div>
</section>
<!-- Utilities Section -->
<section class="category-section mb-12" id="utilities">
<h2 class="text-2xl font-bold text-gray-800 mb-6 pb-2 border-b-2 border-blue-500">Utilities</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- Tool 13: Countdown Timer -->
<div class="tool-card bg-white rounded-lg shadow-md p-6">
<h3 class="text-xl font-bold text-gray-800 mb-3">13. Countdown Timer</h3>
<div class="tool-container">
<div class="mb-4 grid grid-cols-3 gap-2">
<div>
<label class="block text-gray-700 mb-1 text-sm">Hours:</label>
<input class="w-full p-2 border rounded" id="timer-hours" max="23" min="0" type="number" value="0" />
</div>
<div>
<label class="block text-gray-700 mb-1 text-sm">Minutes:</label>
<input class="w-full p-2 border rounded" id="timer-minutes" max="59" min="0" type="number" value="5" />
</div>
<div>
<label class="block text-gray-700 mb-1 text-sm">Seconds:</label>
<input class="w-full p-2 border rounded" id="timer-seconds" max="59" min="0" type="number" value="0" />
</div>
</div>
<div class="mb-4">
<label class="block text-gray-700 mb-1">Timer Label (optional):</label>
<input class="w-full p-2 border rounded" id="timer-label" placeholder="e.g., Break Time" type="text" />
</div>
<div class="mb-4">
<label class="inline-flex items-center">
<input checked="" class="form-checkbox" id="timer-sound" type="checkbox" />
<span class="ml-2">Play Sound When Done</span>
</label>
</div>
<div class="bg-gray-800 p-4 rounded-lg mb-4 text-center">
<p class="text-white mb-1" id="countdown-label"></p>
<p class="text-4xl font-bold text-white" id="countdown-display">00:05:00</p>
</div>
<div class="grid grid-cols-3 gap-2">
<button class="bg-green-500 text-white py-2 px-4 rounded hover:bg-green-600" id="start-timer">Start</button>
<button class="bg-yellow-500 text-white py-2 px-4 rounded hover:bg-yellow-600" disabled="" id="pause-timer">Pause</button>
<button class="bg-red-500 text-white py-2 px-4 rounded hover:bg-red-600" disabled="" id="reset-timer">Reset</button>
</div>
</div>
</div>
<!-- Tool 14: JSON Formatter -->
<div class="tool-card bg-white rounded-lg shadow-md p-6">
<h3 class="text-xl font-bold text-gray-800 mb-3">14. JSON Formatter</h3>
<div class="tool-container">
<div class="mb-3">
<label class="block text-gray-700 mb-1">Enter JSON:</label>
<textarea class="w-full h-40 p-2 border rounded font-mono text-sm" id="json-input" placeholder="{"example": "Paste your JSON here"}"></textarea>
</div>
<div class="grid grid-cols-2 gap-2 mb-3">
<button class="bg-blue-500 text-white py-2 px-3 rounded hover:bg-blue-600" id="format-json">Format JSON</button>
<button class="bg-green-500 text-white py-2 px-3 rounded hover:bg-green-600" id="minify-json">Minify JSON</button>
</div>
<div class="flex justify-between items-center mb-2">
<p class="text-sm font-bold">Formatted JSON:</p>
<button class="text-sm text-blue-600 hover:text-blue-800" id="copy-json">
<i class="fas fa-copy"></i> Copy
</button>
</div>
<div class="relative">
<pre class="w-full h-40 p-2 border rounded bg-gray-50 overflow-auto font-mono text-sm" id="json-output"></pre>
<div class="absolute top-0 left-0 w-full p-2 bg-red-100 text-red-800 rounded hidden" id="json-error"></div>
</div>
</div>
</div>
<!-- Tool 15: Markdown Preview -->
<div class="tool-card bg-white rounded-lg shadow-md p-6">
<h3 class="text-xl font-bold text-gray-800 mb-3">15. Markdown Preview</h3>
<div class="tool-container">
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block text-gray-700 mb-1">Markdown Input:</label>
<textarea class="w-full h-64 p-2 border rounded font-mono text-sm" id="markdown-input" placeholder="Type your markdown here..."></textarea>
</div>
<div>
<label class="block text-gray-700 mb-1">Preview:</label>
<div class="w-full h-64 p-2 border rounded bg-gray-50 overflow-auto" id="markdown-output"></div>
</div>
</div>
<div class="mt-3 grid grid-cols-3 gap-2">
<button class="bg-gray-200 py-1 px-2 rounded text-sm hover:bg-gray-300" id="sample-markdown">Load Sample</button>
<button class="bg-blue-500 text-white py-1 px-2 rounded text-sm hover:bg-blue-600" id="copy-markdown-html">Copy HTML</button>
<button class="bg-red-500 text-white py-1 px-2 rounded text-sm hover:bg-red-600" id="clear-markdown">Clear</button>
</div>
</div>
</div>
</div>
</section>
<!-- Footer -->
<footer class="bg-white shadow-md rounded-lg p-6 text-center text-gray-600">
<p>Digital Tools Hub - A collection of 15 essential web tools for everyday tasks</p>
<p class="text-sm mt-2">All tools are designed to work directly in your browser - no installations required.</p>
</footer>
</div>
<!-- Scripts -->
<script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.1/build/qrcode.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked@4.3.0/marked.min.js"></script>
<script>
// Global error handler
window.addEventListener('error', function(event) {
console.error('Global error:', event.error);
});
document.addEventListener('DOMContentLoaded', function() {
// Utility for showing copy alerts
function showCopyAlert(button) {
const originalText = button.innerHTML;
button.innerHTML = '<i class="fas fa-check mr-1"></i> Copied!';
button.classList.add('bg-green-200');
setTimeout(() => {
button.innerHTML = originalText;
button.classList.remove('bg-green-200');
}, 1500);
}
// =============== TEXT TOOLS ===============
// Tool 1: Text Case Converter
try {
const caseConverterInput = document.getElementById('case-converter-input');
const uppercaseBtn = document.getElementById('uppercase-btn');
const lowercaseBtn = document.getElementById('lowercase-btn');
const capitalizeBtn = document.getElementById('capitalize-btn');
const sentenceCaseBtn = document.getElementById('sentence-case-btn');
const copyCaseConverterBtn = document.getElementById('copy-case-converter');
uppercaseBtn.addEventListener('click', function() {
caseConverterInput.value = caseConverterInput.value.toUpperCase();
});
lowercaseBtn.addEventListener('click', function() {
caseConverterInput.value = caseConverterInput.value.toLowerCase();
});
capitalizeBtn.addEventListener('click', function() {
caseConverterInput.value = caseConverterInput.value
.toLowerCase()
.split(' ')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
});
sentenceCaseBtn.addEventListener('click', function() {
caseConverterInput.value = caseConverterInput.value
.toLowerCase()
.replace(/(^\s*\w|[.!?]\s*\w)/g, function(c) {
return c.toUpperCase();
});
});
copyCaseConverterBtn.addEventListener('click', function() {
caseConverterInput.select();
document.execCommand('copy');
showCopyAlert(copyCaseConverterBtn);
});
} catch (err) {
console.error("Error initializing Text Case Converter:", err);
}
// Tool 2: Character/Word Counter
try {
const textCounterInput = document.getElementById('text-counter-input');
const charCount = document.getElementById('char-count');
const wordCount = document.getElementById('word-count');
textCounterInput.addEventListener('input', function() {
const text = textCounterInput.value;
charCount.textContent = text.length;
// Count words by splitting on whitespace and filtering out empty strings
const words = text.trim().split(/\s+/).filter(word => word.length > 0);
wordCount.textContent = words.length || 0;
});
} catch (err) {
console.error("Error initializing Character/Word Counter:", err);
}
// Tool 3: Find and Replace
try {
const findReplaceText = document.getElementById('find-replace-text');
const findText = document.getElementById('find-text');
const replaceText = document.getElementById('replace-text');
const caseSensitive = document.getElementById('case-sensitive');
const replaceButton = document.getElementById('replace-button');
replaceButton.addEventListener('click', function() {
const text = findReplaceText.value;
const find = findText.value;
const replace = replaceText.value;
if (!find) return;
if (caseSensitive.checked) {
findReplaceText.value = text.split(find).join(replace);
} else {
findReplaceText.value = text.replace(new RegExp(escapeRegExp(find), 'gi'), replace);
}
});
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
} catch (err) {
console.error("Error initializing Find and Replace:", err);
}
// =============== CONVERTERS ===============
// Tool 4: Length Converter
try {
const lengthValue = document.getElementById('length-value');
const lengthFrom = document.getElementById('length-from');
const lengthTo = document.getElementById('length-to');
const convertLengthBtn = document.getElementById('convert-length');
const lengthResult = document.getElementById('length-result');
const lengthConversions = {
mm: 1,
cm: 10,
m: 1000,
km: 1000000,
in: 25.4,
ft: 304.8,
yd: 914.4,
mi: 1609344
};
convertLengthBtn.addEventListener('click', function() {
const fromValue = parseFloat(lengthValue.value);
const fromUnit = lengthFrom.value;
const toUnit = lengthTo.value;
if (isNaN(fromValue)) return;
// Convert to base unit (mm) then to target unit
const inMm = fromValue * lengthConversions[fromUnit];
const result = inMm / lengthConversions[toUnit];
lengthResult.textContent = result.toFixed(5) + " " + toUnit;
});
// Initial conversion
convertLengthBtn.click();
} catch (err) {
console.error("Error initializing Length Converter:", err);
}
// Tool 5: Temperature Converter
try {
const celsiusInput = document.getElementById('celsius');
const fahrenheitInput = document.getElementById('fahrenheit');
const kelvinInput = document.getElementById('kelvin');
celsiusInput.addEventListener('input', function() {
const celsius = parseFloat(celsiusInput.value);
if (!isNaN(celsius)) {
fahrenheitInput.value = (celsius * 9/5 + 32).toFixed(2);
kelvinInput.value = (celsius + 273.15).toFixed(2);
}
});
fahrenheitInput.addEventListener('input', function() {
const fahrenheit = parseFloat(fahrenheitInput.value);
if (!isNaN(fahrenheit)) {
celsiusInput.value = ((fahrenheit - 32) * 5/9).toFixed(2);
kelvinInput.value = ((fahrenheit - 32) * 5/9 + 273.15).toFixed(2);
}
});
kelvinInput.addEventListener('input', function() {
const kelvin = parseFloat(kelvinInput.value);
if (!isNaN(kelvin)) {
celsiusInput.value = (kelvin - 273.15).toFixed(2);
fahrenheitInput.value = ((kelvin - 273.15) * 9/5 + 32).toFixed(2);
}
});
} catch (err) {
console.error("Error initializing Temperature Converter:", err);
}
// Tool 6: Base64 Encoder/Decoder
try {
const base64Input = document.getElementById('base64-input');
const base64Output = document.getElementById('base64-output');
const encodeBase64Btn = document.getElementById('encode-base64');
const decodeBase64Btn = document.getElementById('decode-base64');
const copyBase64Btn = document.getElementById('copy-base64');
encodeBase64Btn.addEventListener('click', function() {
try {
// Use TextEncoder for better UTF-8 support
const text = base64Input.value;
base64Output.value = btoa(unescape(encodeURIComponent(text)));
} catch (e) {
base64Output.value = 'Error: ' + e.message;
}
});
decodeBase64Btn.addEventListener('click', function() {
try {
const base64 = base64Input.value.trim();
base64Output.value = decodeURIComponent(escape(atob(base64)));
} catch (e) {
base64Output.value = 'Error: Invalid base64 string';
}
});
copyBase64Btn.addEventListener('click', function() {
base64Output.select();
document.execCommand('copy');
showCopyAlert(copyBase64Btn);
});
} catch (err) {
console.error("Error initializing Base64 Encoder/Decoder:", err);
}
// =============== CALCULATORS ===============
// Tool 7: Basic Calculator
try {
const calcDisplay = document.getElementById('calc-display');
const calcButtons = document.querySelectorAll('.calc-btn');
const calcEquals = document.getElementById('calc-equals');
const calcClear = document.getElementById('calc-clear');
const calcBackspace = document.getElementById('calc-backspace');
let currentCalcInput = '0';
let previousCalcInput = '';
let calcOperation = null;
let resetCalcScreen = false;
function updateCalcDisplay() {
calcDisplay.value = currentCalcInput;
}
calcButtons.forEach(button => {
button.addEventListener('click', function() {
const value = this.dataset.value;
if (resetCalcScreen) {
currentCalcInput = '';
resetCalcScreen = false;
}
// If the current display is '0', replace it unless we're adding a decimal
if (currentCalcInput === '0' && value !== '.') {
currentCalcInput = value;
} else {
// Don't allow multiple decimal points
if (value === '.' && currentCalcInput.includes('.')) return;
// If it's an operator
if (['+', '-', '*', '/'].includes(value)) {
if (calcOperation !== null) {
// Calculate previous operation first
calcEquals.click();
}
// Store previous input and operation
previousCalcInput = currentCalcInput;
calcOperation = value;
resetCalcScreen = true;
updateCalcDisplay();
return;
}
currentCalcInput += value;
}
updateCalcDisplay();
});
});
calcEquals.addEventListener('click', function() {
if (!calcOperation || resetCalcScreen) return;
const current = parseFloat(currentCalcInput);
const previous = parseFloat(previousCalcInput);
if (isNaN(current) || isNaN(previous)) return;
let result;
switch (calcOperation) {
case '+':
result = previous + current;
break;
case '-':
result = previous - current;
break;
case '*':
result = previous * current;
break;
case '/':
if (current === 0) {
result = 'Error';
} else {
result = previous / current;
}
break;
}
currentCalcInput = String(result);
calcOperation = null;
previousCalcInput = '';
updateCalcDisplay();
});
calcClear.addEventListener('click', function() {
currentCalcInput = '0';
previousCalcInput = '';
calcOperation = null;
resetCalcScreen = false;
updateCalcDisplay();
});
calcBackspace.addEventListener('click', function() {
if (currentCalcInput.length <= 1) {
currentCalcInput = '0';
} else {
currentCalcInput = currentCalcInput.slice(0, -1);
}
updateCalcDisplay();
});
// Initialize calculator display
updateCalcDisplay();
} catch (err) {
console.error("Error initializing Basic Calculator:", err);
}
// Tool 8: Percentage Calculator
try {
const percentX = document.getElementById('percent-x');
const percentY = document.getElementById('percent-y');
const percentResult1 = document.getElementById('percent-result1');
const xValue = document.getElementById('x-value');
const yValue = document.getElementById('y-value');
const percentResult2 = document.getElementById('percent-result2');
const resultX = document.getElementById('result-x');
const percentOfWhat = document.getElementById('percent-of-what');
const percentResult3 = document.getElementById('percent-result3');
const calculatePercentageBtn = document.getElementById('calculate-percentage');
function calculateAllPercentages() {
// What is X% of Y?
const x = parseFloat(percentX.value);
const y = parseFloat(percentY.value);
if (!isNaN(x) && !isNaN(y)) {
percentResult1.value = ((x * y) / 100).toFixed(2);
}
// X is what % of Y?
const xVal = parseFloat(xValue.value);
const yVal = parseFloat(yValue.value);
if (!isNaN(xVal) && !isNaN(yVal) && yVal !== 0) {
percentResult2.value = ((xVal / yVal) * 100).toFixed(2) + '%';
}
// X is Y% of what?
const resX = parseFloat(resultX.value);
const percOfWhat = parseFloat(percentOfWhat.value);
if (!isNaN(resX) && !isNaN(percOfWhat) && percOfWhat !== 0) {
percentResult3.value = (resX * 100 / percOfWhat).toFixed(2);
}
}
// Auto-calculate as values change
percentX.addEventListener('input', calculateAllPercentages);
percentY.addEventListener('input', calculateAllPercentages);
xValue.addEventListener('input', calculateAllPercentages);
yValue.addEventListener('input', calculateAllPercentages);
resultX.addEventListener('input', calculateAllPercentages);
percentOfWhat.addEventListener('input', calculateAllPercentages);
// Button click handler
calculatePercentageBtn.addEventListener('click', calculateAllPercentages);
// Initial calculation
calculateAllPercentages();
} catch (err) {
console.error("Error initializing Percentage Calculator:", err);
}
// Tool 9: Tip Calculator
try {
const billAmount = document.getElementById('bill-amount');
const tipPercentageSlider = document.getElementById('tip-percentage-slider');
const tipPercentageValue = document.getElementById('tip-percentage-value');
const numPeople = document.getElementById('num-people');
const tipAmount = document.getElementById('tip-amount');
const totalWithTip = document.getElementById('total-with-tip');
const tipPerPerson = document.getElementById('tip-per-person');
const totalPerPerson = document.getElementById('total-per-person');
const calculateTipBtn = document.getElementById('calculate-tip');
const tipBtns = document.querySelectorAll('.tip-btn');
function calculateTip() {
const bill = parseFloat(billAmount.value) || 0;
const tipPercent = parseFloat(tipPercentageSlider.value) || 0;
const people = parseInt(numPeople.value) || 1;
if (bill < 0) {
alert('Please enter a valid bill amount.');
billAmount.value = 0;
return;
}
const tip = bill * (tipPercent / 100);
const total = bill + tip;
tipAmount.textContent = '$' + tip.toFixed(2);
totalWithTip.textContent = '$' + total.toFixed(2);
tipPerPerson.textContent = '$' + (tip / people).toFixed(2);
totalPerPerson.textContent = '$' + (total / people).toFixed(2);
}
tipPercentageSlider.addEventListener('input', function() {
tipPercentageValue.textContent = this.value + '%';
calculateTip();
});
tipBtns.forEach(btn => {
btn.addEventListener('click', function() {
const value = this.dataset.value;
tipPercentageSlider.value = value;
tipPercentageValue.textContent = value + '%';
calculateTip();
});
});
billAmount.addEventListener('input', calculateTip);
numPeople.addEventListener('input', calculateTip);
calculateTipBtn.addEventListener('click', calculateTip);
// Initial calculation
calculateTip();
} catch (err) {
console.error("Error initializing Tip Calculator:", err);
}
// =============== GENERATORS ===============
// Tool 10: Password Generator
try {
const passwordLength = document.getElementById('password-length');
const passwordLengthDisplay = document.getElementById('password-length-display');
const includeUppercase = document.getElementById('include-uppercase');
const includeLowercase = document.getElementById('include-lowercase');
const includeNumbers = document.getElementById('include-numbers');
const includeSymbols = document.getElementById('include-symbols');
const generatePasswordBtn = document.getElementById('generate-password');
const passwordOutput = document.getElementById('password-output');
const copyPasswordBtn = document.getElementById('copy-password');
const passwordStrength = document.getElementById('password-strength');
const passwordStrengthText = document.getElementById('password-strength-text');
passwordLength.addEventListener('input', function() {
passwordLengthDisplay.textContent = this.value;
});
function generatePassword() {
const length = parseInt(passwordLength.value);
const useUpper = includeUppercase.checked;
const useLower = includeLowercase.checked;
const useNumbers = includeNumbers.checked;
const useSymbols = includeSymbols.checked;
if (!useUpper && !useLower && !useNumbers && !useSymbols) {
alert('Please select at least one character type.');
return;
}
const uppercaseChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const lowercaseChars = 'abcdefghijklmnopqrstuvwxyz';
const numberChars = '0123456789';
const symbolChars = '!@#$%^&*()-_=+[]{}|;:,.<>?/';
let allChars = '';
if (useUpper) allChars += uppercaseChars;
if (useLower) allChars += lowercaseChars;
if (useNumbers) allChars += numberChars;
if (useSymbols) allChars += symbolChars;
let password = '';
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * allChars.length);
password += allChars[randomIndex];
}
// Ensure at least one of each selected character type is included
let finalPassword = password;
let charPos = 0;
if (useUpper && !/[A-Z]/.test(finalPassword)) {
const randomUppercase = uppercaseChars[Math.floor(Math.random() * uppercaseChars.length)];
finalPassword = randomUppercase + finalPassword.slice(1);
charPos++;
}
if (useLower && !/[a-z]/.test(finalPassword)) {
const randomLowercase = lowercaseChars[Math.floor(Math.random() * lowercaseChars.length)];
finalPassword = finalPassword.slice(0, charPos) + randomLowercase + finalPassword.slice(charPos + 1);
charPos++;
}
if (useNumbers && !/[0-9]/.test(finalPassword)) {
const randomNumber = numberChars[Math.floor(Math.random() * numberChars.length)];
finalPassword = finalPassword.slice(0, charPos) + randomNumber + finalPassword.slice(charPos + 1);
charPos++;
}
if (useSymbols && !/[!@#$%^&*()-_=+[\]{}|;:,.<>?/]/.test(finalPassword)) {
const randomSymbol = symbolChars[Math.floor(Math.random() * symbolChars.length)];
finalPassword = finalPassword.slice(0, charPos) + randomSymbol + finalPassword.slice(charPos + 1);
}
passwordOutput.value = finalPassword;
// Calculate strength
let strength = 0;
if (length >= 8) strength += 1;
if (length >= 12) strength += 1;
if (useUpper) strength += 1;
if (useLower) strength += 1;
if (useNumbers) strength += 1;
if (useSymbols) strength += 1;
// Update strength meter
if (strength <= 2) {
passwordStrength.style.width = '25%';
passwordStrength.className = 'bg-red-500 h-2.5 rounded-full';
passwordStrengthText.textContent = 'Weak';
} else if (strength <= 4) {
passwordStrength.style.width = '50%';
passwordStrength.className = 'bg-yellow-500 h-2.5 rounded-full';
passwordStrengthText.textContent = 'Medium';
} else if (strength <= 5) {
passwordStrength.style.width = '75%';
passwordStrength.className = 'bg-blue-500 h-2.5 rounded-full';
passwordStrengthText.textContent = 'Strong';
} else {
passwordStrength.style.width = '100%';
passwordStrength.className = 'bg-green-500 h-2.5 rounded-full';
passwordStrengthText.textContent = 'Very Strong';
}
}
generatePasswordBtn.addEventListener('click', generatePassword);
copyPasswordBtn.addEventListener('click', function() {
passwordOutput.select();
document.execCommand('copy');
alert('Password copied to clipboard!');
});
// Generate initial password
generatePassword();
} catch (err) {
console.error("Error initializing Password Generator:", err);
}
// Tool 11: QR Code Generator
try {
const qrContent = document.getElementById('qr-content');
const qrSize = document.getElementById('qr-size');
const generateQRBtn = document.getElementById('generate-qr');
const qrCodeResult = document.getElementById('qr-code-result');
const downloadQRBtn = document.getElementById('download-qr');
generateQRBtn.addEventListener('click', function() {
const content = qrContent.value.trim();
if (!content) {
alert('Please enter content for the QR code.');
return;
}
qrCodeResult.innerHTML = '';
const size = parseInt(qrSize.value);
try {
QRCode.toCanvas(document.createElement('canvas'), content, { width: size, margin: 1 }, function(error, canvas) {
if (error) {
console.error('Error generating QR code:', error);
qrCodeResult.innerHTML = '<p class="text-red-500">Error generating QR code</p>';
return;
}
qrCodeResult.appendChild(canvas);
downloadQRBtn.disabled = false;
});
} catch (e) {
console.error('QR Code generation error:', e);
alert('Failed to generate QR code. The QRCode library may not be properly loaded.');
qrCodeResult.innerHTML = '<p class="text-red-500">Error: QR Code library not available</p>';
}
});
downloadQRBtn.addEventListener('click', function() {
const canvas = qrCodeResult.querySelector('canvas');
if (!canvas) return;
const link = document.createElement('a');
link.download = 'qrcode.png';
link.href = canvas.toDataURL('image/png');
link.click();
});
// Generate initial QR code
if (qrContent.value.trim()) {
generateQRBtn.click();
}
} catch (err) {
console.error("Error initializing QR Code Generator:", err);
}
// Tool 12: Color Palette Generator
try {
const baseColor = document.getElementById('base-color');
const colorHex = document.getElementById('color-hex');
const paletteType = document.getElementById('palette-type');
const colorCount = document.getElementById('color-count');
const generatePaletteBtn = document.getElementById('generate-palette');
const paletteResult = document.getElementById('palette-result');
const colorCodes = document.getElementById('color-codes');
// Synchronize color input and text input
baseColor.addEventListener('input', function() {
colorHex.value = this.value;
});
colorHex.addEventListener('input', function() {
// Ensure hex color format
if (/^#[0-9A-F]{6}$/i.test(this.value)) {
baseColor.value = this.value;
}
});
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
function rgbToHex(r, g, b) {
r = Math.min(255, Math.max(0, Math.round(r)));
g = Math.min(255, Math.max(0, Math.round(g)));
b = Math.min(255, Math.max(0, Math.round(b)));
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
function rgbToHsl(r, g, b) {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b);
const 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 };
}
function hslToRgb(h, s, l) {
h /= 360;
s /= 100;
l /= 100;
let r, g, b;
if (s === 0) {
r = g = b = l; // achromatic
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255)
};
}
function generatePalette() {
try {
const baseHex = baseColor.value;
const type = paletteType.value;
const count = parseInt(colorCount.value);
if (!baseHex.match(/^#[0-9A-F]{6}$/i)) {
alert('Please enter a valid hex color code');
return;
}
const rgb = hexToRgb(baseHex);
const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
let colors = [baseHex];
switch (type) {
case 'analogous':
// Generate colors with hues adjacent to the base color
const hueStep = 30;
for (let i = 1; i < count; i++) {
const newHue = (hsl.h + i * hueStep) % 360;
const newRgb = hslToRgb(newHue, hsl.s, hsl.l);
colors.push(rgbToHex(newRgb.r, newRgb.g, newRgb.b));
}
break;
case 'monochromatic':
// Generate colors with same hue but different lightness/saturation
for (let i = 1; i < count; i++) {
const newLightness = Math.max(10, Math.min(90, hsl.l - 20 + (i * 40 / (count - 1))));
const newRgb = hslToRgb(hsl.h, hsl.s, newLightness);
colors.push(rgbToHex(newRgb.r, newRgb.g, newRgb.b));
}
break;
case 'triadic':
// Generate 3 colors evenly spaced around the color wheel
for (let i = 1; i < count; i++) {
const newHue = (hsl.h + i * 120) % 360;
const newRgb = hslToRgb(newHue, hsl.s, hsl.l);
colors.push(rgbToHex(newRgb.r, newRgb.g, newRgb.b));
}
break;
case 'complementary':
// Generate color pairs that are opposite on the color wheel
for (let i = 1; i < count; i++) {
const newHue = (hsl.h + 180) % 360;
const newLightness = Math.max(30, Math.min(70, hsl.l - 10 + (i * 20 / (count - 1))));
const newRgb = hslToRgb(newHue, hsl.s, newLightness);
colors.push(rgbToHex(newRgb.r, newRgb.g, newRgb.b));
}
break;
case 'split':
// Generate colors using split-complementary scheme
for (let i = 1; i < count; i++) {
const angle = i % 2 === 0 ? 150 : 210;
const newHue = (hsl.h + angle) % 360;
const newRgb = hslToRgb(newHue, hsl.s, hsl.l);
colors.push(rgbToHex(newRgb.r, newRgb.g, newRgb.b));
}
break;
case 'random':
// Generate random colors
for (let i = 1; i < count; i++) {
const r = Math.floor(Math.random() * 256);
const g = Math.floor(Math.random() * 256);
const b = Math.floor(Math.random() * 256);
colors.push(rgbToHex(r, g, b));
}
break;
}
// Limit to the requested number of colors
colors = colors.slice(0, count);
// Display the palette
paletteResult.innerHTML = '';
colorCodes.innerHTML = '';
colors.forEach(color => {
const colorDiv = document.createElement('div');
colorDiv.style.backgroundColor = color;
colorDiv.style.flexGrow = 1;
paletteResult.appendChild(colorDiv);
const colorCodeDiv = document.createElement('div');
colorCodeDiv.className = 'text-center text-xs mt-1 mb-2';
// Clone the color swatch for each color
const colorSwatch = document.createElement('div');
colorSwatch.className = 'color-swatch';
colorSwatch.style.backgroundColor = color;
colorSwatch.title = 'Click to copy';
colorSwatch.addEventListener('click', function() {
navigator.clipboard.writeText(color);
alert(`Color ${color} copied to clipboard!`);
});
const colorText = document.createElement('div');
colorText.className = 'cursor-pointer hover:text-blue-500';
colorText.textContent = color;
colorText.title = 'Click to copy';
colorText.addEventListener('click', function() {
navigator.clipboard.writeText(color);
alert(`Color ${color} copied to clipboard!`);
});
colorCodeDiv.appendChild(colorSwatch);
colorCodeDiv.appendChild(colorText);
colorCodes.appendChild(colorCodeDiv);
});
} catch (e) {
console.error('Palette generation error:', e);
alert('Failed to generate color palette: ' + e.message);
}
}
generatePaletteBtn.addEventListener('click', generatePalette);
// Initialize palette
generatePalette();
} catch (err) {
console.error("Error initializing Color Palette Generator:", err);
}
// =============== UTILITIES ===============
// Tool 13: Countdown Timer
try {
const timerHours = document.getElementById('timer-hours');
const timerMinutes = document.getElementById('timer-minutes');
const timerSeconds = document.getElementById('timer-seconds');
const timerLabel = document.getElementById('timer-label');
const timerSound = document.getElementById('timer-sound');
const countdownLabel = document.getElementById('countdown-label');
const countdownDisplay = document.getElementById('countdown-display');
const startTimerBtn = document.getElementById('start-timer');
const pauseTimerBtn = document.getElementById('pause-timer');
const resetTimerBtn = document.getElementById('reset-timer');
let countdownInterval;
let timerEndTime;
let timerPaused = false;
let timeRemaining = 0;
function updateTimerDisplay(totalSeconds) {
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
countdownDisplay.textContent =
(hours < 10 ? '0' : '') + hours + ':' +
(minutes < 10 ? '0' : '') + minutes + ':' +
(seconds < 10 ? '0' : '') + seconds;
countdownLabel.textContent = timerLabel.value || '';
}
function updateTimerControls(running) {
if (running) {
startTimerBtn.disabled = true;
pauseTimerBtn.disabled = false;
resetTimerBtn.disabled = false;
timerHours.disabled = true;
timerMinutes.disabled = true;
timerSeconds.disabled = true;
} else {
startTimerBtn.disabled = false;
pauseTimerBtn.disabled = true;
timerHours.disabled = false;
timerMinutes.disabled = false;
timerSeconds.disabled = false;
}
}
function startTimer() {
if (timerPaused) {
// Resume timer
timerEndTime = new Date().getTime() + timeRemaining * 1000;
timerPaused = false;
} else {
// Start new timer
const hours = parseInt(timerHours.value) || 0;
const minutes = parseInt(timerMinutes.value) || 0;
const seconds = parseInt(timerSeconds.value) || 0;
const totalSeconds = hours * 3600 + minutes * 60 + seconds;
if (totalSeconds <= 0) {
alert('Please enter a valid time greater than zero.');
return;
}
timeRemaining = totalSeconds;
timerEndTime = new Date().getTime() + totalSeconds * 1000;
}
updateTimerControls(true);
countdownInterval = setInterval(function() {
const now = new Date().getTime();
const distance = timerEndTime - now;
if (distance <= 0) {
clearInterval(countdownInterval);
updateTimerDisplay(0);
resetTimerBtn.disabled = false;
pauseTimerBtn.disabled = true;
// Play sound if enabled
if (timerSound.checked) {
try {
// Create audio context and oscillator for beep sound
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioCtx.createOscillator();
const gainNode = audioCtx.createGain();
oscillator.type = 'sine';
oscillator.frequency.value = 800;
gainNode.gain.value = 0.5;
oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);
oscillator.start();
// Beep for 1 second
setTimeout(function() {
oscillator.stop();
}, 1000);
} catch (e) {
console.error('Error playing timer sound:', e);
}
}
return;
}
timeRemaining = Math.floor(distance / 1000);
updateTimerDisplay(timeRemaining);
}, 1000);
}
function pauseTimer() {
clearInterval(countdownInterval);
timerPaused = true;
pauseTimerBtn.disabled = true;
startTimerBtn.disabled = false;
}
function resetTimer() {
clearInterval(countdownInterval);
timerPaused = false;
const hours = parseInt(timerHours.value) || 0;
const minutes = parseInt(timerMinutes.value) || 0;
const seconds = parseInt(timerSeconds.value) || 0;
updateTimerDisplay(hours * 3600 + minutes * 60 + seconds);
updateTimerControls(false);
resetTimerBtn.disabled = true;
}
startTimerBtn.addEventListener('click', startTimer);
pauseTimerBtn.addEventListener('click', pauseTimer);
resetTimerBtn.addEventListener('click', resetTimer);
// Initialize timer display
updateTimerDisplay(parseInt(timerHours.value) * 3600 + parseInt(timerMinutes.value) * 60 + parseInt(timerSeconds.value));
} catch (err) {
console.error("Error initializing Countdown Timer:", err);
}
// Tool 14: JSON Formatter
try {
const jsonInput = document.getElementById('json-input');
const jsonOutput = document.getElementById('json-output');
const formatJsonBtn = document.getElementById('format-json');
const minifyJsonBtn = document.getElementById('minify-json');
const copyJsonBtn = document.getElementById('copy-json');
const jsonError = document.getElementById('json-error');
function formatJSON() {
const input = jsonInput.value.trim();
if (!input) {
jsonOutput.textContent = '';
return;
}
try {
const parsed = JSON.parse(input);
const formatted = JSON.stringify(parsed, null, 2);
jsonOutput.textContent = formatted;
jsonError.classList.add('hidden');
} catch (e) {
jsonError.textContent = 'Invalid JSON: ' + e.message;
jsonError.classList.remove('hidden');
}
}
function minifyJSON() {
const input = jsonInput.value.trim();
if (!input) {
jsonOutput.textContent = '';
return;
}
try {
const parsed = JSON.parse(input);
const minified = JSON.stringify(parsed);
jsonOutput.textContent = minified;
jsonError.classList.add('hidden');
} catch (e) {
jsonError.textContent = 'Invalid JSON: ' + e.message;
jsonError.classList.remove('hidden');
}
}
formatJsonBtn.addEventListener('click', formatJSON);
minifyJsonBtn.addEventListener('click', minifyJSON);
copyJsonBtn.addEventListener('click', function() {
const output = jsonOutput.textContent;
if (!output) return;
navigator.clipboard.writeText(output)
.then(() => {
const originalText = copyJsonBtn.innerHTML;
copyJsonBtn.innerHTML = '<i class="fas fa-check"></i> Copied!';
setTimeout(() => {
copyJsonBtn.innerHTML = originalText;
}, 1500);
})
.catch(err => {
console.error('Failed to copy: ', err);
// Fallback to old method
const textarea = document.createElement('textarea');
textarea.value = output;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
const originalText = copyJsonBtn.innerHTML;
copyJsonBtn.innerHTML = '<i class="fas fa-check"></i> Copied!';
setTimeout(() => {
copyJsonBtn.innerHTML = originalText;
}, 1500);
});
});
} catch (err) {
console.error("Error initializing JSON Formatter:", err);
}
// Tool 15: Markdown Preview
try {
const markdownInput = document.getElementById('markdown-input');
const markdownOutput = document.getElementById('markdown-output');
const sampleMarkdownBtn = document.getElementById('sample-markdown');
const copyMarkdownHtmlBtn = document.getElementById('copy-markdown-html');
const clearMarkdownBtn = document.getElementById('clear-markdown');
// Configure marked renderer
if (typeof marked !== 'undefined') {
marked.setOptions({
breaks: true,
gfm: true
});
function renderMarkdown() {
try {
const markdown = markdownInput.value;
const html = marked.parse(markdown);
markdownOutput.innerHTML = html;
} catch (e) {
console.error('Markdown rendering error:', e);
markdownOutput.innerHTML = '<p class="text-red-500">Error rendering markdown: ' + e.message + '</p>';
}
}
markdownInput.addEventListener('input', renderMarkdown);
sampleMarkdownBtn.addEventListener('click', function() {
markdownInput.value = `# Markdown Example
## Formatted text
This is **bold** and this is *italic*.
This is ~~strikethrough~~ and this is \`inline code\`.
## Lists
### Unordered list
- Item 1
- Item 2
- Nested item
- Another nested item
- Item 3
### Ordered list
1. First item
2. Second item
3. Third item
## Links and Images
[Visit Google](https://www.google.com)
## Code block
\`\`\`javascript
function sayHello() {
console.log("Hello, world!");
}
\`\`\`
## Table
| Header 1 | Header 2 | Header 3 |
|----------|----------|----------|
| Cell 1 | Cell 2 | Cell 3 |
| Cell 4 | Cell 5 | Cell 6 |
## Blockquote
> This is a blockquote.
> It can span multiple lines.
## Horizontal rule
---
`;
renderMarkdown();
});
copyMarkdownHtmlBtn.addEventListener('click', function() {
const html = markdownOutput.innerHTML;
navigator.clipboard.writeText(html)
.then(() => {
const originalText = copyMarkdownHtmlBtn.textContent;
copyMarkdownHtmlBtn.textContent = 'Copied!';
setTimeout(() => {
copyMarkdownHtmlBtn.textContent = originalText;
}, 1500);
})
.catch(err => {
console.error('Failed to copy: ', err);
// Fallback
const textarea = document.createElement('textarea');
textarea.value = html;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
const originalText = copyMarkdownHtmlBtn.textContent;
copyMarkdownHtmlBtn.textContent = 'Copied!';
setTimeout(() => {
copyMarkdownHtmlBtn.textContent = originalText;
}, 1500);
});
});
clearMarkdownBtn.addEventListener('click', function() {
markdownInput.value = '';
renderMarkdown();
});
} else {
markdownOutput.innerHTML = '<p class="text-red-500">Markdown library not loaded. Please check your internet connection.</p>';
sampleMarkdownBtn.disabled = true;
copyMarkdownHtmlBtn.disabled = true;
}
} catch (err) {
console.error("Error initializing Markdown Preview:", err);
}
});
</script>
</body>
</html>