605 lines
16 KiB
HTML
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>
|