Kopiere nachfolgenden Code und speichere ihn als HTML-Datei ab.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Meine Link-Clipboard Liste Pro</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css">
<style>
body { background: #f9f5ee; font-family: system-ui, -apple-system, sans-serif; }
.section-header {
font-size: 1.75rem; font-weight: 700; color: #c2410c;
margin-bottom: 1.5rem; padding-bottom: 0.4rem;
border-bottom: 3px solid #fb923c; display: inline-block;
}
.no-select { user-select: none; }
.link-card { transition: all 0.2s ease; }
/* Versteckt das Standard-Input-Feld für den Import */
#importInput { display: none; }
</style>
</head>
<body class="p-6 md:p-10 max-w-6xl mx-auto min-h-screen">
<h1 class="text-4xl md:text-5xl font-extrabold text-orange-700 text-center mb-10">
Meine Link-Clipboard Liste
</h1>
<div class="mb-8 max-w-md mx-auto">
<div class="relative">
<input type="text" id="searchInput" oninput="render()" placeholder="Suchen..."
class="w-full px-5 py-3 rounded-2xl border-2 border-orange-200 focus:border-orange-500 outline-none shadow-sm transition-all">
<i class="fas fa-search absolute right-4 top-4 text-orange-300"></i>
</div>
</div>
<div class="mb-12">
<div class="flex justify-between items-center mb-6">
<h2 class="section-header">Links</h2>
<button onclick="addLink()" class="bg-orange-600 hover:bg-orange-700 text-white font-medium py-3 px-6 rounded-xl shadow-md flex items-center gap-2">
<i class="fas fa-plus"></i> Link hinzufügen
</button>
</div>
<div id="linksContainer" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"></div>
</div>
<div>
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6">
<h2 class="section-header">Texte zum Kopieren</h2>
<div class="flex flex-wrap gap-3">
<button onclick="addSection()" class="bg-orange-100 hover:bg-orange-200 text-orange-700 px-4 py-2 rounded-xl transition font-bold">+ Neuer Bereich</button>
<div class="flex gap-2">
<button onclick="exportData()" class="bg-slate-600 hover:bg-slate-700 text-white px-4 py-2 rounded-xl transition flex items-center gap-2">
<i class="fas fa-download"></i> Backup
</button>
<button onclick="importData()" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-xl transition flex items-center gap-2">
<i class="fas fa-upload"></i> Import
</button>
<input type="file" id="importInput" accept=".json" onchange="handleFileSelect(event)">
</div>
</div>
</div>
<div id="sectionsContainer" class="space-y-10"></div>
</div>
<button onclick="saveAll()" id="floatSave" class="fixed bottom-6 right-6 z-50 bg-emerald-600 hover:bg-emerald-700 text-white px-6 py-4 rounded-full shadow-2xl flex items-center gap-3 text-lg transition-all hover:scale-105">
<i class="fas fa-save"></i> Speichern
</button>
<script>
// DATEN INITIALISIEREN
let links = JSON.parse(localStorage.getItem('myLinks')) || [];
let sections = JSON.parse(localStorage.getItem('mySections')) || [];
// SPEICHER-FUNKTION
function saveAll(silent = false) {
localStorage.setItem('myLinks', JSON.stringify(links));
localStorage.setItem('mySections', JSON.stringify(sections));
if (silent) return;
const btn = document.getElementById('floatSave');
const orig = btn.innerHTML;
btn.innerHTML = '<i class="fas fa-check"></i> Gespeichert!';
btn.classList.replace('bg-emerald-600', 'bg-emerald-800');
setTimeout(() => {
btn.innerHTML = orig;
btn.classList.replace('bg-emerald-800', 'bg-emerald-600');
}, 1500);
}
// VERSCHIEBE-LOGIK (Universell)
function moveItem(type, index, direction, sectionIdx = null) {
let targetArr = (type === 'link') ? links : (type === 'section') ? sections : sections[sectionIdx].buttons;
const newIndex = index + direction;
if (newIndex < 0 || newIndex >= targetArr.length) return;
const temp = targetArr[index];
targetArr[index] = targetArr[newIndex];
targetArr[newIndex] = temp;
saveAll(true);
render();
}
// RENDERING DER SEITE
function render() {
const search = document.getElementById('searchInput').value.toLowerCase();
// Links Rendering
document.getElementById('linksContainer').innerHTML = links.map((l, idx) => {
if (search && !l.name.toLowerCase().includes(search)) return '';
return `
<div class="link-card bg-gradient-to-br from-orange-500 to-orange-600 text-white p-5 rounded-2xl shadow-md group flex justify-between items-center transition-all">
<a href="${l.url}" target="_blank" class="font-medium truncate pr-2">${l.name}</a>
<div class="flex gap-2 opacity-0 group-hover:opacity-100 transition-opacity no-select shrink-0">
<button onclick="moveItem('link', ${idx}, -1)" title="Nach links" class="p-1 hover:text-orange-200"><i class="fas fa-chevron-left text-xs"></i></button>
<button onclick="moveItem('link', ${idx}, 1)" title="Nach rechts" class="p-1 hover:text-orange-200"><i class="fas fa-chevron-right text-xs"></i></button>
<button onclick="editLink(${l.id})" title="Bearbeiten" class="p-1 hover:text-orange-200"><i class="fas fa-edit text-xs"></i></button>
<button onclick="deleteLink(${l.id})" title="Löschen" class="p-1 hover:text-red-200"><i class="fas fa-trash-alt text-xs"></i></button>
</div>
</div>
`;
}).join('');
// Sektionen Rendering
document.getElementById('sectionsContainer').innerHTML = sections.map((sec, sIdx) => {
const hasVisibleButtons = sec.buttons.some(b => !search || b.label.toLowerCase().includes(search) || b.copy.toLowerCase().includes(search));
if (search && !sec.title.toLowerCase().includes(search) && !hasVisibleButtons) return '';
return `
<div class="bg-white/80 backdrop-blur-sm shadow-xl rounded-3xl p-6 border border-orange-200/50">
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6">
<div class="flex items-center gap-4 w-full sm:w-3/5">
<div class="flex flex-col gap-1 no-select shrink-0">
<button onclick="moveItem('section', ${sIdx}, -1)" class="text-orange-300 hover:text-orange-600"><i class="fas fa-chevron-up text-xs"></i></button>
<button onclick="moveItem('section', ${sIdx}, 1)" class="text-orange-300 hover:text-orange-600"><i class="fas fa-chevron-down text-xs"></i></button>
</div>
<input value="${sec.title}" class="text-2xl font-bold text-orange-800 bg-transparent border-b-2 border-orange-400 focus:outline-none focus:border-orange-600 w-full"
onchange="sections[${sIdx}].title = this.value; saveAll(true)">
</div>
<div class="flex gap-3 self-end sm:self-center">
<button onclick="addButton(${sIdx})" class="text-sm bg-orange-100 hover:bg-orange-200 text-orange-700 px-4 py-2 rounded-xl transition font-bold">+ Button</button>
<button onclick="deleteSection(${sIdx})" class="text-red-400 hover:text-red-600 text-sm"><i class="fas fa-trash-alt"></i></button>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
${sec.buttons.map((btn, bIdx) => {
if (search && !btn.label.toLowerCase().includes(search) && !btn.copy.toLowerCase().includes(search)) return '';
return `
<div class="group relative">
<button onclick="copyText('${btn.copy.replace(/'/g, "\\'")}', this)"
class="w-full bg-gradient-to-br from-orange-500 to-orange-600 hover:from-orange-600 hover:to-orange-700 text-white font-medium py-4 px-5 rounded-2xl shadow-md transition-all flex items-center justify-between gap-3">
<span class="truncate">${btn.label}</span>
<span class="text-xl opacity-80">📋</span>
</button>
<div class="absolute -top-2 -right-2 flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity z-10 no-select">
<button onclick="moveItem('button', ${bIdx}, -1, ${sIdx})" class="bg-white text-orange-700 w-7 h-7 rounded-full shadow-lg flex items-center justify-center hover:scale-110"><i class="fas fa-chevron-left text-[10px]"></i></button>
<button onclick="moveItem('button', ${bIdx}, 1, ${sIdx})" class="bg-white text-orange-700 w-7 h-7 rounded-full shadow-lg flex items-center justify-center hover:scale-110"><i class="fas fa-chevron-right text-[10px]"></i></button>
<button onclick="editButton(${sIdx}, ${bIdx})" class="bg-white text-orange-700 w-7 h-7 rounded-full shadow-lg flex items-center justify-center hover:scale-110"><i class="fas fa-edit text-[10px]"></i></button>
<button onclick="deleteButton(${sIdx}, ${bIdx})" class="bg-white text-red-600 w-7 h-7 rounded-full shadow-lg flex items-center justify-center hover:scale-110"><i class="fas fa-trash-alt text-[10px]"></i></button>
</div>
</div>`;
}).join('')}
</div>
</div>
`;
}).join('');
}
// --- LOGIK FUNKTIONEN ---
function addLink() {
const n = prompt("Name:"); const u = prompt("URL:");
if (n && u) { links.push({ id: Date.now(), name: n, url: u }); saveAll(true); render(); }
}
function editLink(id) {
const l = links.find(x => x.id === id);
const n = prompt("Name:", l.name); const u = prompt("URL:", l.url);
if (n) l.name = n; if (u) l.url = u; saveAll(true); render();
}
function deleteLink(id) { if(confirm("Link löschen?")) { links = links.filter(x => x.id !== id); saveAll(true); render(); } }
function addSection() {
const t = prompt("Bereich Name:");
if (t) { sections.push({ title: t, buttons: [] }); saveAll(true); render(); }
}
function deleteSection(idx) { if(confirm("Ganzen Bereich löschen?")) { sections.splice(idx, 1); saveAll(true); render(); } }
function addButton(sIdx) {
const l = prompt("Button Name:"); const c = prompt("Inhalt zum Kopieren:");
if (l && c) { sections[sIdx].buttons.push({ label: l, copy: c }); saveAll(true); render(); }
}
function editButton(sIdx, bIdx) {
const b = sections[sIdx].buttons[bIdx];
const l = prompt("Name:", b.label); const c = prompt("Inhalt:", b.copy);
if (l) b.label = l; if (c) b.copy = c; saveAll(true); render();
}
function deleteButton(sIdx, bIdx) { if(confirm("Button löschen?")) { sections[sIdx].buttons.splice(bIdx, 1); saveAll(true); render(); } }
function copyText(text, button) {
navigator.clipboard.writeText(text).then(() => {
const origH = button.innerHTML; const origC = button.className;
button.className = origC.replace(/orange/g, 'emerald');
button.innerHTML = '<span>Kopiert!</span><span>✓</span>';
setTimeout(() => { button.className = origC; button.innerHTML = origH; }, 1200);
});
}
// --- BACKUP & IMPORT LOGIK ---
function exportData() {
const data = JSON.stringify({ links, sections }, null, 2);
const blob = new Blob([data], { type: 'application/json' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = `clipboard_backup_${new Date().toISOString().slice(0,10)}.json`;
a.click();
}
function importData() {
document.getElementById('importInput').click();
}
function handleFileSelect(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
try {
const imported = JSON.parse(e.target.result);
if (imported.links && imported.sections) {
if (confirm("Backup laden? Aktuelle Daten werden überschrieben.")) {
links = imported.links;
sections = imported.sections;
saveAll(true);
render();
alert("Import erfolgreich!");
}
} else {
alert("Fehler: Die Datei hat nicht das richtige Format.");
}
} catch (err) {
alert("Fehler beim Lesen der Datei: " + err.message);
}
};
reader.readAsText(file);
// Input zurücksetzen, damit die gleiche Datei nochmal gewählt werden kann
event.target.value = '';
}
render();
</script>
</body>
</html>