Files
hardfiles/www/index.html
e b03f5c5e11 refactor: complete rewrite of hardfiles
Rebuilt from scratch in Go with streaming uploads (5GB support),
password protection, rate limiting, secure shredding, and a
retro-chaotic UI with random GIF backgrounds.
2026-03-28 01:34:36 -04:00

503 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>HARDFILES</title>
<link rel="icon" href="/static/fist.ico">
<style>
@font-face {
font-family: 'VT323';
src: url('/static/vt323.woff2') format('woff2');
font-weight: 400;
}
:root {
--bg: #000000;
--overlay: rgba(0,0,0,0.45);
--red: #FF0000;
--cyan: #00FFFF;
--green: #00FF00;
--yellow: #FFFF00;
--text: #FFFFFF;
--text-dim: #888888;
--font: 'VT323', monospace;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
height: 100%;
overflow: hidden;
background: var(--bg);
color: var(--text);
font-family: var(--font);
font-size: 20px;
}
#bg {
position: fixed;
inset: 0;
background-image: url('{{.BgURL}}');
background-size: cover;
background-position: center;
opacity: 0;
transition: opacity 0.5s ease;
z-index: 0;
}
#bg.loaded { opacity: 1; }
body::before {
content: '';
position: fixed;
inset: 0;
background: var(--overlay);
pointer-events: none;
z-index: 1;
}
body::after {
content: '';
position: fixed;
inset: 0;
background: repeating-linear-gradient(
to bottom,
transparent 0px,
transparent 2px,
rgba(0,0,0,0.12) 2px,
rgba(0,0,0,0.12) 3px
);
pointer-events: none;
z-index: 2;
}
.wrap {
position: relative;
z-index: 3;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1rem;
gap: 0.5rem;
}
/* ---- Logo glitch ---- */
.logo-wrap {
position: relative;
width: 50%;
max-width: 600px;
margin-bottom: 0.5rem;
}
@media (max-width: 768px) { .logo-wrap { width: 90%; } }
.logo-wrap img {
width: 100%;
display: block;
position: relative;
z-index: 1;
}
.logo-wrap::before,
.logo-wrap::after {
content: '';
position: absolute;
inset: 0;
background-image: url('/static/header.png');
background-size: 100% 100%;
background-repeat: no-repeat;
z-index: 0;
}
.logo-wrap::before {
animation: glitch-before 3s infinite steps(1);
}
.logo-wrap::after {
animation: glitch-after 3s infinite steps(1);
animation-delay: 0.15s;
}
@keyframes glitch-before {
0%,4% { opacity: 0; }
5% { opacity: 0.8; transform: translate(-5px, 2px); filter: hue-rotate(90deg);
clip-path: polygon(0 15%, 100% 15%, 100% 35%, 0 35%); }
7% { opacity: 0.8; transform: translate(5px, -2px); filter: hue-rotate(180deg);
clip-path: polygon(0 55%, 100% 55%, 100% 75%, 0 75%); }
8%,29% { opacity: 0; }
30% { opacity: 0.7; transform: translate(-3px, -1px); filter: hue-rotate(270deg);
clip-path: polygon(0 40%, 100% 40%, 100% 60%, 0 60%); }
32%,59% { opacity: 0; }
60% { opacity: 0.9; transform: translate(6px, 1px); filter: hue-rotate(120deg);
clip-path: polygon(0 10%, 100% 10%, 100% 25%, 0 25%); }
61% { opacity: 0.9; transform: translate(-4px, 3px); filter: hue-rotate(200deg);
clip-path: polygon(0 70%, 100% 70%, 100% 90%, 0 90%); }
63%,84% { opacity: 0; }
85% { opacity: 0.7; transform: translate(3px, -2px); filter: hue-rotate(45deg);
clip-path: polygon(0 30%, 100% 30%, 100% 50%, 0 50%); }
87%,100%{ opacity: 0; }
}
@keyframes glitch-after {
0%,14% { opacity: 0; }
15% { opacity: 0.7; transform: translate(4px, 2px); filter: hue-rotate(300deg);
clip-path: polygon(0 20%, 100% 20%, 100% 45%, 0 45%); }
17%,44% { opacity: 0; }
45% { opacity: 0.8; transform: translate(-6px, -1px); filter: hue-rotate(60deg);
clip-path: polygon(0 60%, 100% 60%, 100% 80%, 0 80%); }
47% { opacity: 0.6; transform: translate(3px, 3px); filter: hue-rotate(150deg);
clip-path: polygon(0 5%, 100% 5%, 100% 20%, 0 20%); }
49%,74% { opacity: 0; }
75% { opacity: 0.9; transform: translate(-5px, 2px); filter: hue-rotate(240deg);
clip-path: polygon(0 45%, 100% 45%, 100% 65%, 0 65%); }
77%,100%{ opacity: 0; }
}
/* ---- Tagline ---- */
.tagline {
color: var(--red);
font-size: 1.4rem;
letter-spacing: 0.15em;
margin-bottom: 0.5rem;
text-align: center;
}
/* ---- Drop zone ---- */
.drop-zone {
position: relative;
width: 440px;
max-width: 90vw;
min-height: 150px;
border: 2px dashed var(--red);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem;
cursor: pointer;
background: transparent;
color: var(--text);
font-family: var(--font);
text-align: center;
animation: pulse-opacity 2s ease-in-out infinite;
transition: background 0.2s, border-style 0.1s;
}
@media (max-width: 768px) {
.drop-zone { width: 90vw; min-height: 180px; }
}
@keyframes pulse-opacity {
0%, 100% { opacity: 1; }
50% { opacity: 0.55; }
}
.drop-zone:hover, .drop-zone.drag-over {
animation: shake 0.3s ease;
opacity: 1;
border-style: solid;
background: rgba(255,0,0,0.07);
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
20% { transform: translateX(-4px); }
40% { transform: translateX(4px); }
60% { transform: translateX(-3px); }
80% { transform: translateX(3px); }
}
.drop-zone.uploading {
animation: pulse-opacity 0.5s ease-in-out infinite;
border-style: dashed;
border-color: var(--red);
}
.drop-zone.success {
animation: none;
opacity: 1;
border-style: solid;
border-color: var(--green);
}
.drop-zone.error-state {
animation: none;
opacity: 1;
border-style: solid;
border-color: var(--red);
}
.drop-main {
font-size: 2rem;
letter-spacing: 0.1em;
margin-bottom: 0.5rem;
}
.drop-sub {
font-size: 1.1rem;
color: var(--text-dim);
}
.drop-url {
font-size: 1.6rem;
color: var(--red);
word-break: break-all;
cursor: pointer;
margin-bottom: 0.5rem;
}
.drop-url:hover { text-decoration: underline; }
.drop-another {
font-size: 1rem;
color: var(--text-dim);
cursor: pointer;
margin-top: 0.5rem;
text-decoration: underline;
background: none;
border: none;
font-family: var(--font);
min-height: 44px;
}
.drop-another:hover { color: var(--text); }
#file-input { display: none; }
/* ---- Password controls ---- */
#pw-row {
display: flex;
align-items: center;
gap: 0.75rem;
margin-top: 0.3rem;
font-size: 1.2rem;
color: var(--text-dim);
min-height: 44px;
}
#pw-row input[type=checkbox] { width: 18px; height: 18px; cursor: pointer; }
#pw-input-wrap { margin-top: 0.5rem; }
#pw-input {
background: #000;
border: 2px dashed var(--red);
color: var(--text);
font-family: var(--font);
font-size: 1.3rem;
padding: 0.5rem 0.8rem;
width: 440px;
max-width: 90vw;
outline: none;
min-height: 44px;
}
#pw-input:focus { border-style: solid; }
/* ---- Curl example ---- */
.curl-box {
margin-top: 0.5rem;
width: 440px;
max-width: 90vw;
border-left: 3px solid var(--green);
padding: 0.75rem 1rem;
background: rgba(0,255,0,0.04);
}
.curl-box code {
color: var(--green);
font-family: var(--font);
font-size: 1.1rem;
text-shadow: 0 0 8px rgba(0,255,0,0.5);
white-space: pre-wrap;
word-break: break-all;
display: block;
}
/* ---- Warning ---- */
.warning {
margin-top: 0.5rem;
color: var(--yellow);
font-size: 1.3rem;
letter-spacing: 0.08em;
text-align: center;
animation: flicker 4s infinite;
}
@keyframes flicker {
0%,95%,100% { opacity: 1; }
96% { opacity: 0.3; }
97% { opacity: 1; }
98% { opacity: 0.5; }
99% { opacity: 1; }
}
/* ---- Live region ---- */
#live-region { position: absolute; left: -9999px; top: 0; }
</style>
</head>
<body>
<div id="bg"></div>
<div class="wrap">
<div class="logo-wrap">
<img src="/static/header.png" alt="HARDFILES">
</div>
<div class="tagline">ephemeral &bull; volatile &bull; gone in 24h</div>
<button class="drop-zone" id="drop-zone" aria-label="Upload file — click, drag, or paste">
<input type="file" id="file-input" aria-hidden="true" tabindex="-1">
<div class="drop-main" id="drop-main">DROP FILE HERE</div>
<div class="drop-sub" id="drop-sub">click to browse &bull; ctrl+v to paste &bull; drag &amp; drop</div>
</button>
<div aria-live="polite" id="live-region"></div>
<div id="pw-row">
<input type="checkbox" id="pw-check" aria-label="Password protect this file">
<label for="pw-check">password protect</label>
</div>
<div id="pw-input-wrap" style="display:none">
<label for="pw-input" style="position:absolute;left:-9999px">File password</label>
<input type="password" id="pw-input" placeholder="set password" autocomplete="new-password" aria-label="File password">
</div>
<div class="curl-box">
<code>curl -F file=@example.png https://hardfiles.org/</code>
</div>
<div class="warning">ALL UPLOADS ARE SHREDDED AFTER 24 HOURS</div>
</div>
<script>
(function() {
// Background fade-in
var bgEl = document.getElementById('bg');
var bgStyle = bgEl.style.backgroundImage;
if (bgStyle && bgStyle !== 'url("")' && bgStyle !== "url('')") {
var url = bgStyle.replace(/url\(["']?([^"')]+)["']?\)/, '$1');
var img = new Image();
img.onload = function() { bgEl.classList.add('loaded'); };
img.onerror = function() { bgEl.classList.add('loaded'); };
img.src = url;
} else {
bgEl.classList.add('loaded');
}
var dropZone = document.getElementById('drop-zone');
var fileInput = document.getElementById('file-input');
var dropMain = document.getElementById('drop-main');
var dropSub = document.getElementById('drop-sub');
var liveRegion = document.getElementById('live-region');
var pwCheck = document.getElementById('pw-check');
var pwInputWrap = document.getElementById('pw-input-wrap');
var pwInput = document.getElementById('pw-input');
// Password toggle
pwCheck.addEventListener('change', function() {
pwInputWrap.style.display = pwCheck.checked ? 'block' : 'none';
if (pwCheck.checked) pwInput.focus();
});
// Click to open file picker
dropZone.addEventListener('click', function(e) {
if (e.target.closest('#pw-row') || e.target.closest('#pw-input-wrap')) return;
if (dropZone.classList.contains('success')) return;
fileInput.click();
});
fileInput.addEventListener('change', function() {
if (fileInput.files && fileInput.files[0]) {
uploadFile(fileInput.files[0]);
}
});
// Drag and drop
dropZone.addEventListener('dragover', function(e) {
e.preventDefault();
dropZone.classList.add('drag-over');
});
dropZone.addEventListener('dragleave', function(e) {
if (!dropZone.contains(e.relatedTarget)) {
dropZone.classList.remove('drag-over');
}
});
dropZone.addEventListener('drop', function(e) {
e.preventDefault();
dropZone.classList.remove('drag-over');
var files = e.dataTransfer && e.dataTransfer.files;
if (files && files[0]) uploadFile(files[0]);
});
// Paste
document.addEventListener('paste', function(e) {
var items = e.clipboardData && e.clipboardData.items;
if (!items) return;
for (var i = 0; i < items.length; i++) {
if (items[i].kind === 'file') {
var file = items[i].getAsFile();
if (file) { uploadFile(file); break; }
}
}
});
function setUploading(pct) {
dropZone.classList.remove('success', 'error-state', 'drag-over');
dropZone.classList.add('uploading');
var txt = (pct !== null && pct !== undefined) ? 'UPLOADING... ' + pct + '%' : 'UPLOADING...';
dropMain.textContent = txt;
dropSub.textContent = '';
liveRegion.textContent = txt;
}
function setSuccess(url) {
dropZone.classList.remove('uploading', 'error-state', 'drag-over');
dropZone.classList.add('success');
// Clear children
while (dropMain.firstChild) dropMain.removeChild(dropMain.firstChild);
while (dropSub.firstChild) dropSub.removeChild(dropSub.firstChild);
var urlEl = document.createElement('div');
urlEl.className = 'drop-url';
urlEl.textContent = url;
urlEl.title = 'Click to copy';
urlEl.addEventListener('click', function(e) {
e.stopPropagation();
if (navigator.clipboard) {
navigator.clipboard.writeText(url).then(function() {
urlEl.textContent = 'COPIED';
setTimeout(function() { urlEl.textContent = url; }, 1200);
});
}
});
var anotherBtn = document.createElement('button');
anotherBtn.className = 'drop-another';
anotherBtn.textContent = 'upload another';
anotherBtn.addEventListener('click', function(e) {
e.stopPropagation();
resetZone();
});
dropMain.appendChild(urlEl);
dropSub.appendChild(anotherBtn);
liveRegion.textContent = 'Upload complete. URL: ' + url;
}
function setError(msg) {
dropZone.classList.remove('uploading', 'success', 'drag-over');
dropZone.classList.add('error-state');
dropMain.textContent = msg || 'UPLOAD FAILED';
dropSub.textContent = '';
liveRegion.textContent = msg || 'Upload failed';
setTimeout(resetZone, 3000);
}
function resetZone() {
dropZone.classList.remove('uploading', 'success', 'error-state', 'drag-over');
dropMain.textContent = 'DROP FILE HERE';
dropSub.textContent = 'click to browse \u2022 ctrl+v to paste \u2022 drag \u0026 drop';
liveRegion.textContent = '';
fileInput.value = '';
}
function uploadFile(file) {
setUploading(0);
var fd = new FormData();
fd.append('file', file);
if (pwCheck.checked && pwInput.value) {
fd.append('password', pwInput.value);
}
var xhr = new XMLHttpRequest();
xhr.open('POST', '/');
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
var pct = Math.round(e.loaded / e.total * 100);
setUploading(pct);
}
};
xhr.onload = function() {
if (xhr.status === 200) {
setSuccess(xhr.responseText.trim());
} else if (xhr.status === 413) {
setError('FILE TOO LARGE');
} else {
setError('UPLOAD FAILED: ' + xhr.status);
}
};
xhr.onerror = function() { setError('NETWORK ERROR'); };
xhr.send(fd);
}
})();
</script>
</body>
</html>