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.
503 lines
14 KiB
HTML
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 • volatile • 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 • ctrl+v to paste • drag & 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>
|