← back to Resize It
web-interface.html
326 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Spoonflower Image Resizer</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gradient-to-br from-purple-600 to-indigo-800 min-h-screen flex items-center justify-center p-6">
<div class="bg-white rounded-2xl shadow-2xl p-8 max-w-2xl w-full">
<!-- Header -->
<div class="text-center mb-8">
<h1 class="text-4xl font-bold text-gray-800 mb-2">🎨 Spoonflower Resizer</h1>
<p class="text-gray-600">Resize designs to 24" width at 150 DPI</p>
</div>
<!-- Drop Zone -->
<div id="dropZone" class="border-4 border-dashed border-purple-300 rounded-xl p-12 text-center bg-purple-50 hover:bg-purple-100 transition-colors cursor-pointer mb-6">
<p class="text-purple-600 text-lg font-semibold mb-2">📁 Drop image file here</p>
<p class="text-gray-500 text-sm">or click to browse</p>
<input type="file" id="fileInput" accept="image/*" class="hidden">
</div>
<!-- OR Divider -->
<div class="flex items-center my-6">
<div class="flex-1 border-t border-gray-300"></div>
<span class="px-4 text-gray-500 text-sm">OR</span>
<div class="flex-1 border-t border-gray-300"></div>
</div>
<!-- Spoonflower URL Input -->
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">Spoonflower Design URL</label>
<input
type="text"
id="urlInput"
placeholder="https://www.spoonflower.com/artists/designs/12345-design-name"
class="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:outline-none focus:border-purple-500 transition-colors"
>
</div>
<!-- Action Buttons -->
<div class="grid grid-cols-2 gap-4 mb-6">
<button
id="resizeBtn"
class="bg-gradient-to-r from-purple-600 to-indigo-600 text-white font-semibold py-3 px-6 rounded-lg hover:shadow-lg transform hover:-translate-y-0.5 transition-all disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none"
>
🔧 Resize and Download
</button>
<button
id="goToSpoonBtn"
class="bg-gradient-to-r from-green-500 to-emerald-600 text-white font-semibold py-3 px-6 rounded-lg hover:shadow-lg transform hover:-translate-y-0.5 transition-all disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none"
disabled
>
🌐 Go To Spoon Page
</button>
</div>
<!-- Spoonflower Link Field (appears after resize) -->
<div id="spoonLinkContainer" class="hidden mb-6 p-4 bg-green-50 border-2 border-green-300 rounded-lg">
<label class="block text-sm font-medium text-green-800 mb-2">✅ Design Link Ready</label>
<div class="flex gap-2">
<input
type="text"
id="spoonLink"
readonly
class="flex-1 px-4 py-2 bg-white border border-green-300 rounded-lg text-gray-700"
>
<button
id="openNewTabBtn"
class="px-6 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors font-semibold"
>
🔗 Open New Tab
</button>
</div>
</div>
<!-- Status Messages -->
<div id="status" class="hidden p-4 rounded-lg mb-6"></div>
<!-- Results Panel -->
<div id="resultsPanel" class="hidden mt-6 p-6 bg-gray-50 rounded-xl border border-gray-200">
<h3 class="text-lg font-bold text-gray-800 mb-4">📊 Resize Results</h3>
<div id="resultsContent"></div>
</div>
<!-- Preview -->
<div id="preview" class="mt-6"></div>
</div>
<script>
const dropZone = document.getElementById('dropZone');
const fileInput = document.getElementById('fileInput');
const urlInput = document.getElementById('urlInput');
const resizeBtn = document.getElementById('resizeBtn');
const goToSpoonBtn = document.getElementById('goToSpoonBtn');
const spoonLinkContainer = document.getElementById('spoonLinkContainer');
const spoonLink = document.getElementById('spoonLink');
const openNewTabBtn = document.getElementById('openNewTabBtn');
const status = document.getElementById('status');
const resultsPanel = document.getElementById('resultsPanel');
const resultsContent = document.getElementById('resultsContent');
const preview = document.getElementById('preview');
let selectedFile = null;
let currentSpoonflowerUrl = null;
let resizedImageUrl = null;
// Drop zone handlers
dropZone.addEventListener('click', () => fileInput.click());
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('border-purple-500', 'bg-purple-200');
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('border-purple-500', 'bg-purple-200');
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('border-purple-500', 'bg-purple-200');
const files = e.dataTransfer.files;
if (files.length > 0) {
handleFile(files[0]);
}
});
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
handleFile(e.target.files[0]);
}
});
function handleFile(file) {
if (!file.type.startsWith('image/')) {
showStatus('error', 'Please select an image file');
return;
}
selectedFile = file;
dropZone.innerHTML = `
<p class="text-green-600 text-lg font-semibold mb-2">✅ ${file.name}</p>
<p class="text-gray-500 text-sm">Click to change</p>
`;
showStatus('success', `Selected: ${file.name} (${(file.size / 1024 / 1024).toFixed(2)} MB)`);
// Show preview
const reader = new FileReader();
reader.onload = (e) => {
preview.innerHTML = `<img src="${e.target.result}" alt="Preview" class="max-w-full rounded-lg shadow-lg mx-auto">`;
};
reader.readAsDataURL(file);
}
// Resize and Download button
resizeBtn.addEventListener('click', async () => {
const url = urlInput.value.trim();
if (!selectedFile && !url) {
showStatus('error', 'Please select a file or enter a Spoonflower URL');
return;
}
resizeBtn.disabled = true;
showStatus('info', '⏳ Processing...');
try {
let result;
if (selectedFile) {
result = await processLocalFile(selectedFile);
} else if (url) {
result = await processSpoonflowerURL(url);
}
// Show results
displayResults(result);
// If Spoonflower URL was provided, use it
if (url && url.includes('spoonflower.com')) {
currentSpoonflowerUrl = url;
goToSpoonBtn.disabled = false;
spoonLink.value = url;
spoonLinkContainer.classList.remove('hidden');
showStatus('success', '✅ Image resized successfully! Opening Spoonflower page...');
// Auto-open Spoonflower page after 1 second
setTimeout(() => {
window.open(url, '_blank');
}, 1000);
} else {
// No URL provided - ask for it
showStatus('success', '✅ Image resized successfully!');
// Prompt for Spoonflower URL
setTimeout(() => {
const spoonUrl = prompt('Enter the Spoonflower design URL to open:');
if (spoonUrl && spoonUrl.includes('spoonflower.com')) {
currentSpoonflowerUrl = spoonUrl;
goToSpoonBtn.disabled = false;
spoonLink.value = spoonUrl;
spoonLinkContainer.classList.remove('hidden');
urlInput.value = spoonUrl;
showStatus('success', '✅ Opening Spoonflower design page...');
window.open(spoonUrl, '_blank');
}
}, 500);
}
} catch (error) {
showStatus('error', error.message);
} finally {
resizeBtn.disabled = false;
}
});
// Go To Spoon Page button
goToSpoonBtn.addEventListener('click', () => {
if (currentSpoonflowerUrl) {
window.open(currentSpoonflowerUrl, '_blank');
showStatus('success', '🌐 Opened Spoonflower design page in new tab');
}
});
// Open New Tab button
openNewTabBtn.addEventListener('click', () => {
const url = spoonLink.value;
if (url) {
window.open(url, '_blank');
showStatus('success', '🌐 Opened Spoonflower design in new tab');
}
});
async function processLocalFile(file) {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/resize', {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error('Failed to process image');
}
return await response.json();
}
async function processSpoonflowerURL(url) {
const response = await fetch('/api/spoonflower', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ url })
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Failed to process Spoonflower URL');
}
return await response.json();
}
function displayResults(result) {
resultsPanel.classList.remove('hidden');
const warningHtml = result.warning ? `
<div class="bg-yellow-50 border-l-4 border-yellow-400 p-3 mt-3">
<p class="text-yellow-800 text-sm">⚠️ ${result.warning}</p>
</div>
` : '';
resultsContent.innerHTML = `
<div class="grid grid-cols-2 gap-4 mb-4">
<div>
<p class="text-sm text-gray-600">Original Size</p>
<p class="font-semibold text-gray-800">${result.original.width} × ${result.original.height} px</p>
</div>
<div>
<p class="text-sm text-gray-600">Resized Size</p>
<p class="font-semibold text-gray-800">${result.resized.width} × ${result.resized.height} px</p>
</div>
<div>
<p class="text-sm text-gray-600">Physical Size</p>
<p class="font-semibold text-gray-800">24" × ${result.resized.heightInches}" @ 150 DPI</p>
</div>
<div>
<p class="text-sm text-gray-600">Scale Factor</p>
<p class="font-semibold text-gray-800">${result.scaleFactor}x</p>
</div>
</div>
${warningHtml}
<a href="${result.downloadUrl}" download
class="mt-4 block w-full bg-blue-600 text-white text-center font-semibold py-3 px-6 rounded-lg hover:bg-blue-700 transition-colors">
💾 Download Resized Image
</a>
`;
resizedImageUrl = result.downloadUrl;
}
function showStatus(type, message) {
status.classList.remove('hidden');
const colors = {
success: 'bg-green-100 border-green-400 text-green-800',
error: 'bg-red-100 border-red-400 text-red-800',
info: 'bg-blue-100 border-blue-400 text-blue-800'
};
status.className = `p-4 rounded-lg mb-6 border-l-4 ${colors[type]}`;
status.textContent = message;
}
</script>
</body>
</html>