Vault/Apps/Dokumenten_Beschrifter.html
2026-08-24 15:38:32 +02:00

605 lines
16 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Dateiname-Generator — Erstellt standardisierte Dateinamen mit Datum, Schlagwörtern und Version.">
<title>Dateiname-Generator</title>
<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=Atkinson+Hyperlegible:wght@400;700&family=Lexend:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--page: #FFF8FA;
--page-accent: #F7E8EE;
--surface: #FFFFFF;
--surface-alt: #FCEEF3;
--surface-strong: #F2DCE5;
--text: #38272F;
--muted: #74656C;
--border: #E1CBD4;
--primary: #D35F87;
--primary-hover: #B4476E;
--secondary: #E9DFE4;
--secondary-hover: #DCCFD6;
--success: #6A8C73;
--success-bg: #E8F3EC;
--accent: #A88FA0;
--accent-hover:#BFA6B7;
}
:root {
--font-body:
"Atkinson Hyperlegible",
"Segoe UI",
Arial,
sans-serif;
--font-ui:
"Lexend",
"Segoe UI",
Arial,
sans-serif;
}
body {
font-family: var(--font-body);
font-size: 18px;
line-height: 1.6;
background: var(--page);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 32px 16px;
}
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 32px 32px;
width: 100%;
max-width: 480px;
box-shadow: 0 2px 16px rgba(56,39,47,0.06);
}
h1 {
font-family: var(--font-ui);
font-size: 2rem;
font-weight: 600;
color: var(--text);
margin-bottom: 4px;
}
.subtitle {
font-family: var(--font-ui);
font-size: 15px;
color: var(--muted);
margin-bottom: 32px;
}
.date-row { margin-bottom: 1.5rem; }
.field-label {
display: block;
font-family: var(--font-ui);
font-size: 16px;
font-weight: 500;
color: var(--muted);
margin-bottom: 8px;
letter-spacing: 0.03em;
text-transform: uppercase;
}
input[type="date"] {
padding: 12px 14px;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--surface-alt);
font-family: var(--font-body);
font-size: 16px;
color: var(--text);
outline: none;
transition: border-color 0.15s;
}
input[type="date"]:focus {
border-color: var(--primary);
outline: 3px solid var(--primary);
}
.boxes-label {
font-family: var(--font-ui);
font-size: 16px;
font-weight: 500;
color: var(--muted);
letter-spacing: 0.03em;
text-transform: uppercase;
margin-bottom: 8px;
}
.boxes-hint {
font-family: var(--font-body);
font-size: 14px;
color: var(--accent);
margin-bottom: 12px;
}
.boxes-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
margin-bottom: 8px;
}
.box-wrap {
display: flex;
flex-direction: column;
gap: 4px;
}
.box-num {
font-family: var(--font-ui);
font-size: 13px;
color: var(--muted);
text-align: center;
}
.word-box {
width: 100%;
padding: 12px 8px;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--surface-alt);
font-family: var(--font-body);
font-size: 16px;
color: var(--text);
outline: none;
transition: border-color 0.15s, background 0.15s;
text-align: center;
}
.word-box:focus {
border-color: var(--primary);
background: var(--surface);
outline: 3px solid var(--primary);
}
.word-box::placeholder {
color: var(--accent);
font-size: 14px;
}
.word-box.has-value {
border-color: var(--secondary);
background: var(--surface-strong);
color: var(--text);
}
.boxes-sub {
font-family: var(--font-body);
font-size: 14px;
color: var(--muted);
margin-bottom: 20px;
line-height: 1.6;
}
.version-section {
margin-bottom: 24px;
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.version-label {
font-family: var(--font-ui);
font-size: 16px;
font-weight: 500;
color: var(--muted);
letter-spacing: 0.03em;
text-transform: uppercase;
white-space: nowrap;
}
.version-btns {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.version-btn {
padding: 8px 12px;
border: 1px solid var(--border);
border-radius: 20px;
background: transparent;
font-family: var(--font-ui);
font-size: 15px;
color: var(--muted);
cursor: pointer;
transition: background 0.1s, color 0.1s;
}
.version-btn:hover { background: var(--page-accent); }
.version-btn.active {
background: var(--surface-alt);
color: var(--text);
border-color: var(--border);
font-weight: 600;
}
.divider {
border: none;
border-top: 1px solid var(--border);
margin: 20px 0;
}
.result-label {
font-family: var(--font-ui);
font-size: 16px;
font-weight: 500;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.03em;
margin-bottom: 8px;
}
.result-box {
background: var(--page-accent);
border: 1px solid var(--border);
border-radius: 6px;
padding: 14px 16px;
font-family: var(--font-ui);
font-size: 18px;
color: var(--text);
font-weight: 600;
min-height: 48px;
display: flex;
align-items: center;
word-break: break-all;
margin-bottom: 12px;
letter-spacing: 0.01em;
line-height: 1.6;
}
.result-box.empty {
color: var(--muted);
font-weight: 400;
font-size: 15px;
}
.result-part { color: var(--primary); }
.result-sep { color: var(--border); margin: 0 1px; font-weight: 400; }
.result-date { color: var(--muted); font-weight: 400; }
.btn-row { display: flex; gap: 8px; }
button.btn-copy {
flex: 1;
padding: 12px 16px;
border: none;
border-radius: 6px;
font-family: var(--font-ui);
font-size: 16px;
font-weight: 500;
cursor: pointer;
background: var(--surface-alt);
color: var(--text);
border: 1px solid var(--border);
transition: background 0.15s, color 0.15s, transform 0.1s;
}
button.btn-copy:hover { background: var(--secondary-hover); }
button.btn-copy:active { transform: scale(0.98); }
button.btn-copy.copied {
background: var(--success-bg);
color: var(--success);
border-color: #C5DDD0;
}
button.btn-reset {
padding: 12px 16px;
border: 1px solid var(--border);
border-radius: 6px;
background: transparent;
font-family: var(--font-ui);
font-size: 16px;
color: var(--muted);
cursor: pointer;
transition: background 0.1s;
}
button.btn-reset:hover { background: var(--page-accent); }
.examples {
margin-top: 24px;
padding-top: 20px;
border-top: 1px solid var(--border);
}
.examples-label {
font-family: var(--font-ui);
font-size: 14px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.03em;
margin-bottom: 8px;
}
.example {
font-family: var(--font-body);
font-size: 15px;
color: var(--muted);
padding: 4px 0;
cursor: pointer;
transition: color 0.1s;
list-style: none;
}
.example:hover { color: var(--primary); }
@media (max-width: 480px) {
.boxes-row { grid-template-columns: repeat(2, 1fr); }
.card { padding: 28px 20px; }
}
</style>
</head>
<body>
<div class="card">
<h1>Dateiname-Generator</h1>
<p class="subtitle">YYYY-MM-DD _ topic _ description _ extra</p>
<div class="date-row">
<label class="field-label" for="dateField">Datum</label>
<input type="date" id="dateField">
</div>
<div class="boxes-label">Schlagwörter</div>
<p class="boxes-hint">Eingabe normal — Leerzeichen werden automatisch zu Bindestrichen</p>
<div class="boxes-row">
<div class="box-wrap">
<span class="box-num">1 · topic</span>
<input class="word-box" id="w1" type="text" placeholder="project alfa" autocomplete="off" spellcheck="false">
</div>
<div class="box-wrap">
<span class="box-num">2 · description</span>
<input class="word-box" id="w2" type="text" placeholder="proposal" autocomplete="off" spellcheck="false">
</div>
<div class="box-wrap">
<span class="box-num">3 · extra</span>
<input class="word-box" id="w3" type="text" placeholder="client xyz" autocomplete="off" spellcheck="false">
</div>
<div class="box-wrap">
<span class="box-num">4 · more</span>
<input class="word-box" id="w4" type="text" placeholder="notes" autocomplete="off" spellcheck="false">
</div>
</div>
<p class="boxes-sub">So viele oder wenige Felder ausfüllen wie nötig · Leere Felder werden übersprungen · Tab springt zwischen den Feldern</p>
<div class="version-section">
<span class="version-label">Version</span>
<div class="version-btns" role="group" aria-label="Version auswählen">
<button class="version-btn" data-version="" aria-pressed="true">none</button>
<button class="version-btn" data-version="v1" aria-pressed="false">v1</button>
<button class="version-btn" data-version="v2" aria-pressed="false">v2</button>
<button class="version-btn" data-version="v3" aria-pressed="false">v3</button>
<button class="version-btn" data-version="draft" aria-pressed="false">draft</button>
<button class="version-btn" data-version="final" aria-pressed="false">final</button>
</div>
</div>
<hr class="divider">
<div class="result-label">Ergebnis</div>
<div class="result-box empty" id="result" role="status" aria-live="polite">Füllen Sie mindestens ein Feld aus …</div>
<div class="btn-row">
<button class="btn-copy" id="copyBtn">Dateiname kopieren</button>
<button class="btn-reset" id="resetBtn">Zurücksetzen</button>
</div>
<div class="examples">
<div class="examples-label">Beispiele — zum Laden klicken</div>
<ul>
<li class="example" data-w1="laptop cleanup" data-w2="notes" data-w3="" data-w4="">→ 2026-06-01_laptop-cleanup_notes</li>
<li class="example" data-w1="project alfa" data-w2="proposal" data-w3="" data-w4="">→ 2026-06-01_project-alfa_proposal</li>
<li class="example" data-w1="finances" data-w2="june overview" data-w3="" data-w4="">→ 2026-06-01_finances_june-overview</li>
<li class="example" data-w1="meeting" data-w2="client xyz" data-w3="notes" data-w4="">→ 2026-06-01_meeting_client-xyz_notes</li>
<li class="example" data-w1="dev notes" data-w2="rust learning" data-w3="chapter 3" data-w4="">→ 2026-06-01_dev-notes_rust-learning_chapter-3</li>
</ul>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
let currentVersion = '';
const dateField = document.getElementById('dateField');
const copyBtn = document.getElementById('copyBtn');
const resetBtn = document.getElementById('resetBtn');
const resultEl = document.getElementById('result');
const wordIds = ['w1', 'w2', 'w3', 'w4'];
function sanitise(str) {
return str
.toLowerCase()
.trim()
.replace(/ä/g, 'ae')
.replace(/ö/g, 'oe')
.replace(/ü/g, 'ue')
.replace(/ß/g, 'ss')
.replace(/\s+/g, '-')
.replace(/[^a-z0-9\-]/g, '')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '');
}
function todayISO() {
const now = new Date();
const yyyy = now.getFullYear();
const mm = String(now.getMonth() + 1).padStart(2, '0');
const dd = String(now.getDate()).padStart(2, '0');
return `${yyyy}-${mm}-${dd}`;
}
dateField.value = todayISO();
function buildParts() {
const date = dateField.value || todayISO();
const words = wordIds.map(id => sanitise(document.getElementById(id).value)).filter(Boolean);
if (currentVersion) words.push(currentVersion);
return { date, words };
}
function buildFilename() {
const { date, words } = buildParts();
if (!words.length) return '';
return [date, ...words].join('_');
}
function update() {
const fn = buildFilename();
wordIds.forEach(id => {
const el = document.getElementById(id);
el.classList.toggle('has-value', sanitise(el.value).length > 0);
});
if (!fn) {
resultEl.textContent = 'Füllen Sie mindestens ein Feld aus …';
resultEl.className = 'result-box empty';
return;
}
const { date, words } = buildParts();
const parts = [date, ...words];
resultEl.innerHTML = '';
parts.forEach((part, i) => {
if (i === 0) {
const span = document.createElement('span');
span.className = 'result-date';
span.textContent = part;
resultEl.appendChild(span);
} else {
const sep = document.createElement('span');
sep.className = 'result-sep';
sep.textContent = '_';
resultEl.appendChild(sep);
const span = document.createElement('span');
span.className = 'result-part';
span.textContent = part;
resultEl.appendChild(span);
}
});
resultEl.className = 'result-box';
}
function setVersion(v) {
currentVersion = v;
document.querySelectorAll('.version-btn').forEach(b => {
const isActive = (b.dataset.version === v);
b.classList.toggle('active', isActive);
b.setAttribute('aria-pressed', String(isActive));
});
update();
}
function copyResult() {
const fn = buildFilename();
if (!fn) return;
navigator.clipboard.writeText(fn).then(() => {
const btn = copyBtn;
const originalText = btn.textContent;
btn.textContent = '✓ Kopiert!';
btn.classList.add('copied');
setTimeout(() => {
btn.textContent = originalText;
btn.classList.remove('copied');
}, 2000);
});
}
function resetAll() {
dateField.value = todayISO();
wordIds.forEach(id => { document.getElementById(id).value = ''; });
setVersion('');
update();
document.getElementById('w1').focus();
}
function loadEx(w1, w2, w3, w4) {
dateField.value = todayISO();
const vals = [w1, w2, w3, w4];
wordIds.forEach((id, i) => {
document.getElementById(id).value = vals[i] || '';
});
setVersion('');
update();
}
// Event listeners
document.querySelector('.version-btns').addEventListener('click', e => {
const btn = e.target.closest('.version-btn');
if (btn) setVersion(btn.dataset.version);
});
document.querySelector('.examples').addEventListener('click', e => {
const ex = e.target.closest('.example');
if (ex) {
loadEx(
ex.dataset.w1 || '',
ex.dataset.w2 || '',
ex.dataset.w3 || '',
ex.dataset.w4 || ''
);
}
});
dateField.addEventListener('input', update);
wordIds.forEach(id => {
const el = document.getElementById(id);
el.addEventListener('input', () => {
const pos = el.selectionStart;
const before = el.value;
const after = before.replace(/ /g, '-');
if (before !== after) {
el.value = after;
el.setSelectionRange(pos, pos);
}
update();
});
el.addEventListener('keydown', e => {
if (e.key === 'Enter') {
e.preventDefault();
copyResult();
}
});
});
document.addEventListener('keydown', e => {
if (e.key === 'Enter' && document.activeElement.tagName !== 'BUTTON') {
copyResult();
}
});
copyBtn.addEventListener('click', copyResult);
resetBtn.addEventListener('click', resetAll);
update();
document.getElementById('w1').focus();
});
</script>
</body>
</html>