Initial commit

This commit is contained in:
2026-07-02 00:53:17 +02:00
commit fd2828f1e7
17 changed files with 1627 additions and 0 deletions
+213
View File
@@ -0,0 +1,213 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Upload — Photo Gallery</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Vollkorn:ital,wght@0,400;0,600;0,700;1,400&family=Manjari:wght@100;400;700&display=swap">
<link rel="stylesheet" href="/static/style.css">
<style>
.upload-page {
max-width: 520px;
margin: 0 auto;
padding: 32px 20px;
}
.upload-page h2 {
font-family: var(--vk);
font-size: 22px;
font-weight: 600;
color: var(--fg);
margin-bottom: 28px;
}
.drop-zone {
border: 1px dashed var(--bg2);
background: var(--bg0);
width: 100%;
aspect-ratio: 1;
max-height: 340px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
margin-bottom: 24px;
position: relative;
overflow: hidden;
transition: border-color 0.2s;
}
.drop-zone:hover { border-color: var(--fg4); }
.drop-zone.has-image { border-style: solid; border-color: var(--bg2); }
.drop-zone input[type="file"] {
position: absolute;
inset: 0;
opacity: 0;
cursor: pointer;
width: 100%;
height: 100%;
}
.drop-prompt {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
color: var(--fg4);
pointer-events: none;
}
.drop-prompt p { font-size: 14px; font-family: var(--vk); }
.drop-prompt small { font-size: 12px; font-family: monospace; }
#preview {
display: none;
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.field { margin-bottom: 18px; }
.field label {
display: block;
font-size: 12px;
color: var(--fg4);
font-family: monospace;
text-transform: uppercase;
letter-spacing: 0.06em;
margin-bottom: 6px;
}
.field input[type="text"],
.field input[type="date"] {
width: 100%;
background: var(--bg0);
border: 1px solid var(--bg2);
color: var(--fg);
font-size: 16px;
padding: 10px 12px;
outline: none;
transition: border-color 0.2s;
}
.field input[type="text"] { font-family: var(--vk); }
.field input[type="date"] { font-family: monospace; font-size: 14px; color-scheme: dark; }
.field input:focus { border-color: var(--fg4); }
.submit-btn {
width: 100%;
background: var(--bg1);
border: 1px solid var(--bg2);
color: var(--fg2);
font-family: var(--vk);
font-size: 16px;
font-weight: 600;
padding: 12px;
cursor: pointer;
transition: background 0.2s, color 0.2s;
}
.submit-btn:hover:not(:disabled) { background: var(--bg2); color: var(--fg); }
.submit-btn:disabled { opacity: 0.35; cursor: not-allowed; }
#feedback {
display: none;
margin-top: 14px;
font-size: 13px;
font-family: monospace;
padding: 10px 12px;
}
#feedback.ok { display: block; background: #1d2e1d; color: #b8bb26; border: 1px solid #344d28; }
#feedback.err { display: block; background: #2e1d1d; color: #fb4934; border: 1px solid #5a2828; }
</style>
</head>
<body>
<div class="upload-page">
<a href="/" class="back-btn">← Gallery</a>
<h2>Upload photo</h2>
<div class="drop-zone" id="drop-zone">
<input type="file" id="file-input" accept=".jpg,.jpeg,.png" aria-label="Choose photo">
<div class="drop-prompt" id="prompt">
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" aria-hidden="true">
<rect x="3" y="3" width="18" height="18" rx="1"/>
<circle cx="8.5" cy="8.5" r="1.5"/>
<path d="M21 15l-5-5L5 21"/>
</svg>
<p>Click to choose a photo</p>
<small>jpg or png</small>
</div>
<img id="preview" alt="Preview">
</div>
<div class="field">
<label for="caption">Caption</label>
<input type="text" id="caption" placeholder="Describe the photo" autocomplete="off">
</div>
<div class="field">
<label for="date">Date</label>
<input type="date" id="date">
</div>
<button class="submit-btn" id="submit-btn" disabled>Upload photo</button>
<div id="feedback"></div>
</div>
<script>
const fileInput = document.getElementById('file-input');
const preview = document.getElementById('preview');
const prompt = document.getElementById('prompt');
const dropZone = document.getElementById('drop-zone');
const submitBtn = document.getElementById('submit-btn');
const feedback = document.getElementById('feedback');
const dateInput = document.getElementById('date');
dateInput.value = new Date().toISOString().slice(0, 10);
fileInput.addEventListener('change', () => {
const file = fileInput.files[0];
if (!file) return;
const url = URL.createObjectURL(file);
preview.onload = () => URL.revokeObjectURL(url);
preview.src = url;
preview.style.display = 'block';
prompt.style.display = 'none';
dropZone.classList.add('has-image');
submitBtn.disabled = false;
feedback.className = '';
feedback.style.display = 'none';
});
submitBtn.addEventListener('click', async () => {
const file = fileInput.files[0];
if (!file) return;
submitBtn.disabled = true;
submitBtn.textContent = 'Uploading…';
feedback.className = '';
const form = new FormData();
form.append('image', file);
form.append('caption', document.getElementById('caption').value);
form.append('date', dateInput.value);
try {
const res = await fetch('/upload', { method: 'POST', body: form });
const text = (await res.text()).trim();
if (res.ok) {
feedback.className = 'ok';
feedback.textContent = '✓ ' + text;
fileInput.value = '';
preview.style.display = 'none';
prompt.style.display = 'flex';
dropZone.classList.remove('has-image');
document.getElementById('caption').value = '';
submitBtn.disabled = true;
} else {
feedback.className = 'err';
feedback.textContent = '✗ ' + text;
submitBtn.disabled = false;
}
} catch (err) {
feedback.className = 'err';
feedback.textContent = '✗ ' + err.message;
submitBtn.disabled = false;
}
submitBtn.textContent = 'Upload photo';
});
</script>
</body>
</html>