Files
superior.webp/index.html
2025-08-21 18:50:22 -07:00

513 lines
17 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebP Is Superior</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
color: #333;
}
.container {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 40px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
max-width: 800px;
width: 90%;
backdrop-filter: blur(10px);
}
h1 {
text-align: center;
color: #2c3e50;
margin-bottom: 30px;
font-size: 2.5em;
font-weight: 700;
background: linear-gradient(135deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.upload-area {
border: 3px dashed #667eea;
border-radius: 15px;
padding: 60px 20px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
background: linear-gradient(135deg, rgba(102, 126, 234, 0.05), rgba(118, 75, 162, 0.05));
position: relative;
overflow: hidden;
}
.upload-area:hover {
border-color: #764ba2;
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1), rgba(118, 75, 162, 0.1));
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}
.upload-area.dragover {
border-color: #27ae60;
background: linear-gradient(135deg, rgba(39, 174, 96, 0.1), rgba(46, 204, 113, 0.1));
transform: scale(1.02);
}
.upload-icon {
font-size: 4em;
color: #667eea;
margin-bottom: 20px;
display: block;
}
.upload-text {
font-size: 1.2em;
color: #555;
margin-bottom: 15px;
}
.upload-subtext {
color: #888;
font-size: 0.9em;
}
#fileInput {
display: none;
}
.quality-controls {
margin: 30px 0;
padding: 25px;
background: rgba(102, 126, 234, 0.05);
border-radius: 15px;
border: 1px solid rgba(102, 126, 234, 0.2);
}
.quality-row {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 15px;
}
.quality-row:last-child {
margin-bottom: 0;
}
.quality-row label {
font-weight: 600;
color: #2c3e50;
min-width: 120px;
}
.quality-slider {
flex: 1;
height: 8px;
border-radius: 4px;
background: #ddd;
outline: none;
transition: background 0.3s;
}
.quality-slider::-webkit-slider-thumb {
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea, #764ba2);
cursor: pointer;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
}
.quality-value {
font-weight: bold;
color: #667eea;
min-width: 60px;
text-align: center;
}
.checkbox-container {
display: flex;
align-items: center;
gap: 10px;
}
.checkbox-container input[type="checkbox"] {
width: 18px;
height: 18px;
accent-color: #667eea;
}
.results {
margin-top: 30px;
}
.file-result {
background: white;
border-radius: 15px;
padding: 25px;
margin-bottom: 20px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
border: 1px solid rgba(102, 126, 234, 0.1);
}
.file-info {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
.info-section h4 {
color: #2c3e50;
margin-bottom: 10px;
font-size: 1.1em;
}
.info-item {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
padding: 5px 0;
border-bottom: 1px solid #f0f0f0;
}
.info-label {
color: #666;
font-weight: 500;
}
.info-value {
color: #2c3e50;
font-weight: 600;
}
.size-reduction {
color: #27ae60;
font-weight: bold;
}
.size-increase {
color: #e74c3c;
font-weight: bold;
}
.download-btn {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border: none;
padding: 12px 30px;
border-radius: 25px;
cursor: pointer;
font-weight: 600;
font-size: 1em;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
}
.download-btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
}
.progress-bar {
width: 100%;
height: 8px;
background: #f0f0f0;
border-radius: 4px;
overflow: hidden;
margin: 20px 0;
}
.progress-fill {
height: 100%;
background: linear-gradient(135deg, #667eea, #764ba2);
width: 0%;
transition: width 0.3s ease;
}
.supported-formats {
margin-top: 20px;
text-align: center;
color: #666;
font-size: 0.9em;
}
.format-list {
color: #667eea;
font-weight: 600;
}
@media (max-width: 600px) {
.container {
padding: 20px;
margin: 20px;
}
.file-info {
grid-template-columns: 1fr;
}
.quality-row {
flex-direction: column;
align-items: stretch;
gap: 10px;
}
.quality-row label {
min-width: auto;
}
}
</style>
</head>
<body>
<div class="container">
<h1>WebP Is Superior.</h1>
<div class="upload-area" onclick="document.getElementById('fileInput').click()">
<div class="upload-icon">📁</div>
<div class="upload-text">Click to select images or drag and drop</div>
<div class="upload-subtext">Convert your images to WebP format with superior quality</div>
</div>
<input type="file" id="fileInput" multiple accept=".jpg,.jpeg,.png,.bmp,.gif,.tiff,.webp,.svg,.ico,.avif,.heic">
<div class="quality-controls">
<div class="quality-row">
<label for="qualitySlider">Quality:</label>
<input type="range" id="qualitySlider" class="quality-slider" min="0" max="100" value="95">
<span class="quality-value" id="qualityValue">95%</span>
</div>
<div class="quality-row">
<div class="checkbox-container">
<input type="checkbox" id="preserveMetadata">
<label for="preserveMetadata">Preserve original filename</label>
</div>
</div>
</div>
<div class="supported-formats">
<strong>Supported formats:</strong>
<span class="format-list">JPEG, PNG, BMP, GIF, TIFF, WebP, SVG, ICO, AVIF, HEIC</span>
</div>
<div id="results" class="results"></div>
</div>
<script>
const uploadArea = document.querySelector('.upload-area');
const fileInput = document.getElementById('fileInput');
const qualitySlider = document.getElementById('qualitySlider');
const qualityValue = document.getElementById('qualityValue');
const preserveMetadata = document.getElementById('preserveMetadata');
const results = document.getElementById('results');
// Update quality display
qualitySlider.addEventListener('input', (e) => {
const value = e.target.value;
if (value == 100) {
qualityValue.textContent = '100% (Lossless)';
} else {
qualityValue.textContent = value + '%';
}
});
// Drag and drop functionality
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('dragover');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
const files = Array.from(e.dataTransfer.files);
processFiles(files);
});
// File input change
fileInput.addEventListener('change', (e) => {
const files = Array.from(e.target.files);
processFiles(files);
});
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function calculateSavings(originalSize, newSize) {
const savings = ((originalSize - newSize) / originalSize) * 100;
return savings.toFixed(1);
}
async function processFiles(files) {
results.innerHTML = '';
for (let i = 0; i < files.length; i++) {
const file = files[i];
await processFile(file, i + 1, files.length);
}
}
async function processFile(file, index, total) {
const resultDiv = document.createElement('div');
resultDiv.className = 'file-result';
resultDiv.innerHTML = `
<h3>Processing: ${file.name}</h3>
<div class="progress-bar">
<div class="progress-fill" style="width: 0%"></div>
</div>
`;
results.appendChild(resultDiv);
const progressFill = resultDiv.querySelector('.progress-fill');
try {
// Simulate progress
progressFill.style.width = '20%';
const img = new Image();
img.crossOrigin = 'anonymous';
const loadPromise = new Promise((resolve, reject) => {
img.onload = resolve;
img.onerror = reject;
});
img.src = URL.createObjectURL(file);
await loadPromise;
progressFill.style.width = '50%';
// Create canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
// Draw image
ctx.drawImage(img, 0, 0);
progressFill.style.width = '80%';
// Convert to WebP
const isLossless = qualitySlider.value == 100;
const quality = isLossless ? 1.0 : qualitySlider.value / 100;
const webpDataUrl = canvas.toDataURL('image/webp', quality);
// Convert data URL to blob for size calculation
const webpBlob = await (await fetch(webpDataUrl)).blob();
progressFill.style.width = '100%';
// Generate filename
const originalName = file.name.substring(0, file.name.lastIndexOf('.')) || file.name;
const webpFilename = preserveMetadata.checked
? `${originalName}.webp`
: `converted_${Date.now()}_${index}.webp`;
// Calculate compression stats
const originalSize = file.size;
const webpSize = webpBlob.size;
const savings = calculateSavings(originalSize, webpSize);
const compressionRatio = (originalSize / webpSize).toFixed(2);
// Update result display
resultDiv.innerHTML = `
<div class="file-info">
<div class="info-section">
<h4>📄 Original File</h4>
<div class="info-item">
<span class="info-label">Name:</span>
<span class="info-value">${file.name}</span>
</div>
<div class="info-item">
<span class="info-label">Size:</span>
<span class="info-value">${formatFileSize(originalSize)}</span>
</div>
<div class="info-item">
<span class="info-label">Type:</span>
<span class="info-value">${file.type || 'Unknown'}</span>
</div>
<div class="info-item">
<span class="info-label">Dimensions:</span>
<span class="info-value">${img.naturalWidth} × ${img.naturalHeight}</span>
</div>
</div>
<div class="info-section">
<h4>🚀 WebP Result</h4>
<div class="info-item">
<span class="info-label">Name:</span>
<span class="info-value">${webpFilename}</span>
</div>
<div class="info-item">
<span class="info-label">Size:</span>
<span class="info-value">${formatFileSize(webpSize)}</span>
</div>
<div class="info-item">
<span class="info-label">Quality:</span>
<span class="info-value">${qualitySlider.value == 100 ? 'Lossless' : qualitySlider.value + '%'}</span>
</div>
<div class="info-item">
<span class="info-label">Size Change:</span>
<span class="info-value ${webpSize < originalSize ? 'size-reduction' : 'size-increase'}">
${webpSize < originalSize ? '-' : '+'}${Math.abs(savings)}%
</span>
</div>
<div class="info-item">
<span class="info-label">Compression:</span>
<span class="info-value">${compressionRatio}:1</span>
</div>
</div>
</div>
<a href="${webpDataUrl}" download="${webpFilename}" class="download-btn">
💾 Download WebP
</a>
`;
// Clean up
URL.revokeObjectURL(img.src);
} catch (error) {
console.error('Error processing file:', error);
resultDiv.innerHTML = `
<h3>❌ Error processing: ${file.name}</h3>
<p style="color: #e74c3c; margin-top: 10px;">
${error.message || 'Failed to convert file. Please ensure it\'s a valid image format.'}
</p>
`;
}
}
</script>
</body>
</html>