commit 7aab5ef739ddd4ba6c81a26726a02d7ad7d51b6b
parent b9ecab05cdd4b0f9d94c07edf72b5713c3d1cdc1
Author: Amit Dutta <mail@amit.is-a.dev>
Date: Mon, 20 Jul 2026 20:41:21 +0530
Update index.html
Diffstat:
| M | index.html | | | 277 | +++++++++++++++++++++++++++++++++++++++---------------------------------------- |
1 file changed, 137 insertions(+), 140 deletions(-)
diff --git a/index.html b/index.html
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Upload Hub</title>
- <!-- This script instantly checks for ?item= and redirects before the page even loads -->
+ <!-- Instant redirect check -->
<script>
const urlParams = new URLSearchParams(window.location.search);
const item = urlParams.get('item');
@@ -14,15 +14,12 @@
}
</script>
- <!-- Fonts: Lexend -->
+ <!-- Fonts: Lexend & Material Symbols -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Lexend:wght@300;400;500;600;700&display=swap" rel="stylesheet">
-
- <!-- Material Symbols (Icons) -->
- <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0" rel="stylesheet" />
+ <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,1,0" rel="stylesheet" />
- <!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
@@ -32,20 +29,38 @@
sans: ['Lexend', 'sans-serif'],
},
colors: {
- // Material 3 Color Palette (Light/Dark adaptive approximations)
+ // Strict M3 Color Palette (Light/Dark)
m3: {
- surface: '#FEF7FF',
- surfaceDark: '#141218',
- surfaceContainer: '#F3EDF7',
- surfaceContainerDark: '#211F26',
- primary: '#6750A4',
- primaryDark: '#D0BCFF',
+ background: '#FDFCFB',
+ backgroundDark: '#1A1C19',
+ surface: '#FDFCFB',
+ surfaceDark: '#1A1C19',
+ surfaceContainer: '#F1F1EA',
+ surfaceContainerDark: '#2D2F2B',
+ surfaceContainerHigh: '#EBEBE4',
+ surfaceContainerHighDark: '#333531',
+ surfaceContainerHighest: '#E5E6DE',
+ surfaceContainerHighestDark: '#434541',
+ onSurface: '#1A1C19',
+ onSurfaceDark: '#E3E3DC',
+ onSurfaceVariant: '#43483E',
+ onSurfaceVariantDark: '#C3C8BC',
+ primary: '#386A20',
+ primaryDark: '#9CD67D',
onPrimary: '#FFFFFF',
- onPrimaryDark: '#381E72',
- secondaryContainer: '#E8DEF8',
- onSecondaryContainer: '#1D192B',
- error: '#B3261E',
- errorDark: '#F2B8B5'
+ onPrimaryDark: '#0C3900',
+ secondaryContainer: '#D7E8CD',
+ secondaryContainerDark: '#374B2E',
+ onSecondaryContainer: '#111F0F',
+ onSecondaryContainerDark: '#D7E8CD',
+ error: '#BA1A1A',
+ errorDark: '#FFB4AB',
+ errorContainer: '#FFDAD6',
+ errorContainerDark: '#93000A',
+ onErrorContainer: '#410002',
+ onErrorContainerDark: '#FFDAD6',
+ outline: '#74796D',
+ outlineDark: '#8D9286',
}
}
}
@@ -54,148 +69,129 @@
</script>
<style>
+ /* Zero animations, instant state changes only */
body {
- /* Fallback background if Tailwind fails to load instantly */
- background-color: #FEF7FF;
+ background-color: #FDFCFB;
+ margin: 0;
+ -webkit-font-smoothing: antialiased;
}
@media (prefers-color-scheme: dark) {
- body { background-color: #141218; }
+ body { background-color: #1A1C19; }
}
- .progress-bar-fill {
- transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
- }
-
- .m3-input {
- transition: all 0.2s ease;
- box-shadow: 0 1px 0 0 #49454F; /* Bottom border effect */
- }
- .m3-input:focus {
- box-shadow: 0 2px 0 0 #6750A4; /* Active bottom border */
+ /* Instant focus states, no transitions */
+ input:focus {
outline: none;
+ border-bottom-color: #386A20;
}
@media (prefers-color-scheme: dark) {
- .m3-input { box-shadow: 0 1px 0 0 #CAC4D0; }
- .m3-input:focus { box-shadow: 0 2px 0 0 #D0BCFF; }
+ input:focus { border-bottom-color: #9CD67D; }
}
- /* Smooth fade ins */
- .fade-in {
- animation: fadeIn 0.3s ease-in forwards;
- }
- @keyframes fadeIn {
- from { opacity: 0; transform: translateY(10px); }
- to { opacity: 1; transform: translateY(0); }
- }
+ /* Utility classes for instant display toggling */
+ .show { display: block !important; }
+ .show-flex { display: flex !important; }
+ .hide { display: none !important; }
</style>
</head>
-<body class="bg-m3-surface dark:bg-m3-surfaceDark text-gray-900 dark:text-gray-100 flex flex-col items-center justify-center min-h-screen p-4 m-0 antialiased font-sans transition-colors duration-300">
+<body class="bg-m3-background dark:bg-m3-backgroundDark text-m3-onSurface dark:text-m3-onSurfaceDark flex flex-col items-center justify-center min-h-screen p-4 font-sans">
- <!-- Main Card Container (Hidden initially to prevent flash before redirect check) -->
- <div id="ui-container" class="bg-m3-surfaceContainer dark:bg-m3-surfaceContainerDark w-full max-w-[480px] rounded-[28px] p-8 shadow-sm transition-colors duration-300" style="display: none;">
+ <div id="ui-container" class="w-full max-w-[480px] hide">
- <!-- Header -->
- <div class="flex items-center gap-3 mb-6">
- <div class="bg-m3-secondaryContainer text-m3-onSecondaryContainer p-3 rounded-xl flex items-center justify-center">
- <span class="material-symbols-outlined">cloud_upload</span>
- </div>
- <div>
- <h1 class="text-xl font-semibold leading-tight">Upload File</h1>
- <p class="text-sm text-gray-600 dark:text-gray-400">Drag & drop or browse</p>
- </div>
+ <!-- Minimal Header -->
+ <div class="mb-8">
+ <h1 class="text-2xl font-bold tracking-tight">Upload</h1>
+ <p class="text-sm text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark mt-1">Store files permanently on Hugging Face</p>
</div>
- <!-- Input Field -->
- <div class="mb-6 relative">
- <input type="text" id="custom-id" placeholder=" " class="m3-input block w-full px-4 pt-5 pb-2 text-base bg-m3-surface dark:bg-[#2B2930] rounded-t-lg peer placeholder-transparent">
- <label for="custom-id" class="absolute text-sm text-gray-500 dark:text-gray-400 duration-300 transform -translate-y-3 scale-75 top-4 z-10 origin-[0] left-4 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-3 peer-focus:text-m3-primary dark:peer-focus:text-m3-primaryDark">Custom link ID (Optional)</label>
+ <!-- Static Input Field -->
+ <div class="flex flex-col gap-1.5 mb-6">
+ <label for="custom-id" class="text-sm font-medium text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark">Custom ID (Optional)</label>
+ <input type="text" id="custom-id" placeholder="e.g., source-code" class="px-4 py-3 bg-m3-surfaceContainerHighest dark:bg-m3-surfaceContainerHighestDark text-m3-onSurface dark:text-m3-onSurfaceDark rounded-t-md border-b-2 border-m3-outline dark:border-m3-outlineDark placeholder-gray-500">
</div>
- <!-- Dropzone -->
- <div id="dropzone" class="border-2 border-dashed border-gray-400 dark:border-gray-600 rounded-[20px] p-8 text-center cursor-pointer hover:bg-m3-surface dark:hover:bg-[#2B2930] hover:border-m3-primary dark:hover:border-m3-primaryDark transition-all duration-200 group relative">
- <span class="material-symbols-outlined text-4xl text-gray-400 group-hover:text-m3-primary dark:group-hover:text-m3-primaryDark mb-3 block transition-colors">upload_file</span>
- <p class="text-sm font-medium mb-1">Click or drag a file here</p>
- <p class="text-xs text-gray-500 dark:text-gray-400">Max limit: 4.5MB (Vercel limits)</p>
+ <!-- Solid M3 Dropzone (No dashed borders, solid surface) -->
+ <div id="dropzone" class="bg-m3-surfaceContainer dark:bg-m3-surfaceContainerDark hover:bg-m3-surfaceContainerHigh dark:hover:bg-m3-surfaceContainerHighDark rounded-[24px] p-10 flex flex-col items-center justify-center cursor-pointer mb-6 border border-transparent">
+ <span class="material-symbols-outlined text-[48px] text-m3-primary dark:text-m3-primaryDark mb-4">upload</span>
+ <span class="text-base font-semibold text-m3-onSurface dark:text-m3-onSurfaceDark">Click or drag file</span>
+ <span class="text-sm mt-1 text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark">Max 4.5MB per file</span>
<input type="file" id="file-input" class="hidden">
</div>
- <!-- Status Area (Progress / Result / Error) -->
- <div id="status-area" class="mt-6 hidden">
-
- <!-- Progress View -->
- <div id="progress-container" class="hidden fade-in">
- <div class="flex justify-between items-center mb-2">
- <span class="text-sm font-medium">Uploading...</span>
- <span id="progress-text" class="text-sm font-semibold text-m3-primary dark:text-m3-primaryDark">0%</span>
- </div>
- <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2 overflow-hidden">
- <div id="progress-bar" class="progress-bar-fill bg-m3-primary dark:bg-m3-primaryDark h-2 rounded-full w-0"></div>
- </div>
+ <!-- Progress Bar (Utilitarian, no animation on width change) -->
+ <div id="progress-container" class="hide mb-6">
+ <div class="flex justify-between items-center mb-2">
+ <span class="text-sm font-semibold text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark">Uploading...</span>
+ <span id="progress-text" class="text-sm font-bold text-m3-primary dark:text-m3-primaryDark">0%</span>
+ </div>
+ <div class="w-full bg-m3-surfaceContainerHighest dark:bg-m3-surfaceContainerHighestDark h-2 rounded-none">
+ <div id="progress-bar" class="bg-m3-primary dark:bg-m3-primaryDark h-2 rounded-none" style="width: 0%;"></div>
</div>
+ </div>
- <!-- Result View -->
- <div id="result-container" class="hidden fade-in bg-m3-surface dark:bg-[#2B2930] rounded-2xl p-4 border border-gray-200 dark:border-gray-700">
- <div class="flex items-center gap-2 mb-3 text-green-600 dark:text-green-400">
- <span class="material-symbols-outlined">check_circle</span>
- <span class="font-semibold text-sm">Upload complete!</span>
- </div>
-
- <div class="bg-m3-surfaceContainer dark:bg-m3-surfaceContainerDark p-3 rounded-xl flex items-center justify-between gap-3 overflow-hidden">
- <a id="final-link" href="#" target="_blank" class="text-sm text-m3-primary dark:text-m3-primaryDark truncate hover:underline font-medium"></a>
- <button id="copy-btn" class="flex-shrink-0 bg-m3-primary dark:bg-m3-primaryDark text-white dark:text-gray-900 rounded-full w-10 h-10 flex items-center justify-center hover:opacity-90 transition-opacity focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-m3-primary" title="Copy to clipboard">
- <span class="material-symbols-outlined text-[20px]" id="copy-icon">content_copy</span>
- </button>
- </div>
+ <!-- Success Result -->
+ <div id="result-container" class="hide bg-m3-secondaryContainer dark:bg-m3-secondaryContainerDark p-4 rounded-[16px] mb-6 border border-transparent">
+ <div class="flex items-center gap-2 mb-3">
+ <span class="material-symbols-outlined text-m3-onSecondaryContainer dark:text-m3-onSecondaryContainerDark">check_circle</span>
+ <span class="font-bold text-m3-onSecondaryContainer dark:text-m3-onSecondaryContainerDark text-sm">Upload Successful</span>
+ </div>
+ <div class="flex items-center gap-2 bg-m3-background dark:bg-m3-backgroundDark p-2 pl-3 rounded-[8px]">
+ <input type="text" id="final-link" readonly class="bg-transparent border-none outline-none text-sm text-m3-onSurface dark:text-m3-onSurfaceDark w-full truncate cursor-default">
+ <button id="copy-btn" class="bg-m3-primary dark:bg-m3-primaryDark text-m3-onPrimary dark:text-m3-onPrimaryDark px-4 py-2 rounded-[8px] text-sm font-medium hover:bg-opacity-90 active:bg-opacity-100 flex items-center gap-2">
+ <span class="material-symbols-outlined text-[18px]" id="copy-icon">content_copy</span>
+ <span>Copy</span>
+ </button>
</div>
+ </div>
- <!-- Error View -->
- <div id="error-container" class="hidden fade-in text-m3-error dark:text-m3-errorDark bg-[#F9DEDC] dark:bg-[#8C1D18] bg-opacity-20 dark:bg-opacity-20 p-4 rounded-2xl flex items-start gap-3">
+ <!-- Error Container -->
+ <div id="error-container" class="hide bg-m3-errorContainer dark:bg-m3-errorContainerDark text-m3-onErrorContainer dark:text-m3-onErrorContainerDark p-4 rounded-[16px] mb-6">
+ <div class="flex items-start gap-3">
<span class="material-symbols-outlined">error</span>
<span id="error-text" class="text-sm font-medium mt-0.5"></span>
</div>
-
</div>
+
</div>
<script>
- // Only show the UI if we aren't redirecting an ?item= request
+ // Reveal UI only if not redirecting
if (!item) {
- document.getElementById('ui-container').style.display = 'block';
+ document.getElementById('ui-container').classList.remove('hide');
+ document.getElementById('ui-container').classList.add('show');
}
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('file-input');
const customIdInput = document.getElementById('custom-id');
- // UI Elements
- const statusArea = document.getElementById('status-area');
const progressContainer = document.getElementById('progress-container');
const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');
const resultContainer = document.getElementById('result-container');
- const finalLink = document.getElementById('final-link');
+ const finalLinkInput = document.getElementById('final-link');
const copyBtn = document.getElementById('copy-btn');
const copyIcon = document.getElementById('copy-icon');
const errorContainer = document.getElementById('error-container');
const errorText = document.getElementById('error-text');
- // Drag and drop event bindings
+ // Drag & Drop Handling (Instant state swaps, no transition classes)
dropzone.addEventListener('click', () => fileInput.click());
dropzone.addEventListener('dragover', (e) => {
e.preventDefault();
- dropzone.classList.add('border-m3-primary', 'dark:border-m3-primaryDark', 'bg-m3-surface', 'dark:bg-[#2B2930]');
+ dropzone.style.borderColor = 'var(--tw-colors-m3-primary)';
});
dropzone.addEventListener('dragleave', () => {
- dropzone.classList.remove('border-m3-primary', 'dark:border-m3-primaryDark', 'bg-m3-surface', 'dark:bg-[#2B2930]');
+ dropzone.style.borderColor = 'transparent';
});
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
- dropzone.classList.remove('border-m3-primary', 'dark:border-m3-primaryDark', 'bg-m3-surface', 'dark:bg-[#2B2930]');
+ dropzone.style.borderColor = 'transparent';
if (e.dataTransfer.files.length) {
uploadFile(e.dataTransfer.files[0]);
}
@@ -207,110 +203,111 @@
}
});
- // Copy button logic
+ // Copy Button Logic (Instant text swap)
copyBtn.addEventListener('click', () => {
- const linkToCopy = finalLink.href;
-
- // Modern clipboard API
+ const linkToCopy = finalLinkInput.value;
navigator.clipboard.writeText(linkToCopy).then(() => {
- // Success visual feedback
copyIcon.textContent = 'check';
- copyBtn.classList.add('bg-green-500', 'dark:bg-green-400');
+ copyBtn.querySelector('span:last-child').textContent = 'Copied';
setTimeout(() => {
copyIcon.textContent = 'content_copy';
- copyBtn.classList.remove('bg-green-500', 'dark:bg-green-400');
+ copyBtn.querySelector('span:last-child').textContent = 'Copy';
}, 2000);
}).catch(err => {
- console.error('Failed to copy: ', err);
- // Fallback for older browsers / strict environments
- const tempInput = document.createElement("input");
- tempInput.value = linkToCopy;
- document.body.appendChild(tempInput);
- tempInput.select();
+ // Fallback
+ finalLinkInput.select();
document.execCommand("copy");
- document.body.removeChild(tempInput);
-
copyIcon.textContent = 'check';
- setTimeout(() => { copyIcon.textContent = 'content_copy'; }, 2000);
+ copyBtn.querySelector('span:last-child').textContent = 'Copied';
+ setTimeout(() => {
+ copyIcon.textContent = 'content_copy';
+ copyBtn.querySelector('span:last-child').textContent = 'Copy';
+ }, 2000);
});
});
function resetUI() {
- statusArea.classList.remove('hidden');
- progressContainer.classList.add('hidden');
- resultContainer.classList.add('hidden');
- errorContainer.classList.add('hidden');
+ progressContainer.classList.remove('show');
+ progressContainer.classList.add('hide');
+ resultContainer.classList.remove('show');
+ resultContainer.classList.add('hide');
+ errorContainer.classList.remove('show-flex');
+ errorContainer.classList.add('hide');
progressBar.style.width = '0%';
progressText.innerText = '0%';
}
+ // Real Progress Upload Logic via XMLHttpRequest
function uploadFile(file) {
resetUI();
if (file.size > 4.5 * 1024 * 1024) {
- errorText.innerText = "Error: File exceeds 4.5MB Vercel limit.";
- errorContainer.classList.remove('hidden');
+ errorText.innerText = "File exceeds 4.5MB serverless limit.";
+ errorContainer.classList.remove('hide');
+ errorContainer.classList.add('show-flex');
return;
}
- // Show progress state
- progressContainer.classList.remove('hidden');
+ progressContainer.classList.remove('hide');
+ progressContainer.classList.add('show');
const formData = new FormData();
formData.append('file', file);
const customId = customIdInput.value.trim();
- if (customId) {
- formData.append('custom_id', customId);
- }
+ if (customId) formData.append('custom_id', customId);
- // Use XMLHttpRequest to track progress natively
const xhr = new XMLHttpRequest();
xhr.open('POST', '/api/upload', true);
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
const percentComplete = Math.round((e.loaded / e.total) * 100);
+ // Instant width update, no CSS transition
progressBar.style.width = percentComplete + '%';
progressText.innerText = percentComplete + '%';
}
};
xhr.onload = function() {
- progressContainer.classList.add('hidden'); // Hide progress bar
+ progressContainer.classList.remove('show');
+ progressContainer.classList.add('hide');
if (xhr.status >= 200 && xhr.status < 300) {
try {
const data = JSON.parse(xhr.responseText);
const finalUrl = `${window.location.origin}/?item=${data.item_id}`;
- finalLink.href = finalUrl;
- finalLink.innerText = finalUrl;
- resultContainer.classList.remove('hidden');
+ finalLinkInput.value = finalUrl;
+ resultContainer.classList.remove('hide');
+ resultContainer.classList.add('show');
- // Reset inputs
fileInput.value = '';
customIdInput.value = '';
} catch(err) {
- errorText.innerText = "Error: Invalid response from server.";
- errorContainer.classList.remove('hidden');
+ errorText.innerText = "Invalid response from server.";
+ errorContainer.classList.remove('hide');
+ errorContainer.classList.add('show-flex');
}
} else {
try {
const errData = JSON.parse(xhr.responseText);
- errorText.innerText = `Error: ${errData.detail || 'Upload failed'}`;
+ errorText.innerText = errData.detail || 'Upload failed.';
} catch(err) {
- errorText.innerText = "Error: Upload failed.";
+ errorText.innerText = "Upload failed.";
}
- errorContainer.classList.remove('hidden');
+ errorContainer.classList.remove('hide');
+ errorContainer.classList.add('show-flex');
}
};
xhr.onerror = function() {
- progressContainer.classList.add('hidden');
- errorText.innerText = "System Error: Could not connect to server.";
- errorContainer.classList.remove('hidden');
+ progressContainer.classList.remove('show');
+ progressContainer.classList.add('hide');
+ errorText.innerText = "System error: Could not connect to server.";
+ errorContainer.classList.remove('hide');
+ errorContainer.classList.add('show-flex');
};
xhr.send(formData);