create credits page
This commit is contained in:
parent
bada4b312a
commit
05ed85bc2e
@ -254,6 +254,33 @@ body::before{content:'';position:fixed;inset:0;background-image:radial-gradient(
|
||||
.toast-danger{background:rgba(192,64,64,.18);border-color:var(--red2);color:var(--red2);}
|
||||
.toast-info{background:rgba(74,122,192,.18);border-color:var(--blue2);color:var(--blue2);}
|
||||
|
||||
|
||||
/* ═══════════ CREDITS PAGE ═══════════ */
|
||||
.credits-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px;margin-bottom:32px;}
|
||||
.credit-card{background:var(--bg2);border:1px solid var(--border2);border-radius:6px;padding:22px 24px;transition:all .22s;}
|
||||
.credit-card:hover{border-color:var(--border3);transform:translateY(-2px);}
|
||||
.credit-card.partner{border-color:rgba(200,169,110,.3);background:linear-gradient(135deg,var(--bg2) 0%,rgba(200,169,110,.04) 100%);}
|
||||
.credit-avatar{width:56px;height:56px;border-radius:50%;object-fit:cover;background:var(--bg4);border:2px solid var(--border2);flex-shrink:0;}
|
||||
.credit-avatar-placeholder{width:56px;height:56px;border-radius:50%;background:var(--bg4);border:2px solid var(--border2);display:flex;align-items:center;justify-content:center;font-size:22px;flex-shrink:0;}
|
||||
.credit-header{display:flex;align-items:center;gap:14px;margin-bottom:12px;}
|
||||
.credit-name{font-family:'Cinzel',serif;font-size:16px;font-weight:600;color:var(--accent2);letter-spacing:.5px;}
|
||||
.credit-role{font-family:'Share Tech Mono',monospace;font-size:9px;letter-spacing:2px;text-transform:uppercase;color:var(--muted);margin-top:3px;}
|
||||
.credit-role.partner-role{color:var(--accent);}
|
||||
.credit-desc{font-size:13.5px;color:var(--muted2);line-height:1.65;}
|
||||
.credit-links{display:flex;gap:8px;margin-top:12px;flex-wrap:wrap;}
|
||||
.credit-link{font-family:'Share Tech Mono',monospace;font-size:9px;letter-spacing:1.5px;text-transform:uppercase;padding:4px 10px;border-radius:2px;background:var(--bg4);border:1px solid var(--border2);color:var(--muted2);text-decoration:none;transition:all .18s;}
|
||||
.credit-link:hover{color:var(--text);border-color:var(--border3);}
|
||||
.credit-link.discord{background:rgba(88,101,242,.12);border-color:rgba(88,101,242,.35);color:#7289da;}
|
||||
.credit-link.discord:hover{background:rgba(88,101,242,.22);}
|
||||
.credit-link.roblox{background:rgba(192,64,64,.1);border-color:rgba(192,64,64,.3);color:var(--red2);}
|
||||
.credit-link.roblox:hover{background:rgba(192,64,64,.2);}
|
||||
.credit-link.website{background:rgba(74,122,192,.1);border-color:rgba(74,122,192,.3);color:var(--blue2);}
|
||||
.credit-link.website:hover{background:rgba(74,122,192,.2);}
|
||||
.partner-badge{font-family:'Share Tech Mono',monospace;font-size:9px;letter-spacing:2px;text-transform:uppercase;padding:2px 8px;border-radius:2px;background:rgba(200,169,110,.15);border:1px solid rgba(200,169,110,.35);color:var(--accent2);margin-left:auto;flex-shrink:0;align-self:flex-start;}
|
||||
.credits-section-title{font-family:'Cinzel',serif;font-size:14px;color:var(--accent);letter-spacing:3px;text-transform:uppercase;margin-bottom:16px;}
|
||||
.credits-empty{text-align:center;padding:40px 24px;color:var(--muted);font-style:italic;font-size:14px;background:var(--bg3);border:1px dashed var(--border2);border-radius:6px;}
|
||||
.add-credit-form{background:var(--bg3);border:1px solid var(--border2);border-radius:6px;padding:22px 24px;margin-bottom:28px;}
|
||||
|
||||
@media(max-width:640px){.site-title{font-size:28px;letter-spacing:4px;}.form-row{grid-template-columns:1fr;}.ghost-grid{grid-template-columns:1fr;}.modal-hd,.modal-body{padding-left:18px;padding-right:18px;}.login-box{padding:32px 24px;}.login-logo{font-size:34px;letter-spacing:6px;}}
|
||||
</style>
|
||||
</head>
|
||||
@ -312,6 +339,7 @@ body::before{content:'';position:fixed;inset:0;background-image:radial-gradient(
|
||||
<button class="nav-btn active">Ghost Cards</button>
|
||||
<button class="nav-btn">Cheatsheet</button>
|
||||
<button class="nav-btn">Hunt Guide</button>
|
||||
<button class="nav-btn">Credits</button>
|
||||
</nav>
|
||||
<div class="panel active" id="vtab-cards">
|
||||
<div class="filter-row" id="ev-filter"><div class="filter-label-sm">Evidence Filter — einmal: gefunden · zweimal: ausgeschlossen</div></div>
|
||||
@ -347,6 +375,28 @@ body::before{content:'';position:fixed;inset:0;background-image:radial-gradient(
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel" id="vtab-credits">
|
||||
<div style="max-width:1000px;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:28px;flex-wrap:wrap;">
|
||||
<div>
|
||||
<div style="font-family:'Cinzel',serif;font-size:26px;font-weight:700;color:var(--accent);letter-spacing:3px;">Credits & Partner</div>
|
||||
<div style="font-family:'Share Tech Mono',monospace;font-size:9px;letter-spacing:3px;color:var(--muted);text-transform:uppercase;margin-top:5px;">Danksagungen · Partner · Community</div>
|
||||
</div>
|
||||
<div id="credits-admin-btn" style="display:none;">
|
||||
<button class="btn-accent" id="btn-add-credit">+ Eintrag hinzufügen</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="credits-partners-section">
|
||||
<div class="divider"><div class="divider-line"></div><div class="divider-text">Partner</div><div class="divider-line"></div></div>
|
||||
<div class="credits-grid" id="credits-partners-grid"></div>
|
||||
</div>
|
||||
<div id="credits-credits-section">
|
||||
<div class="divider"><div class="divider-line"></div><div class="divider-text">Credits</div><div class="divider-line"></div></div>
|
||||
<div class="credits-grid" id="credits-credits-grid"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ADMIN -->
|
||||
@ -447,6 +497,77 @@ body::before{content:'';position:fixed;inset:0;background-image:radial-gradient(
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Credits Editor Modal -->
|
||||
<div class="modal-bg" id="credits-modal">
|
||||
<div class="modal-box" style="max-width:600px;">
|
||||
<button class="modal-close" id="close-credits-modal">✕</button>
|
||||
<div class="modal-hd">
|
||||
<div class="modal-title" id="cm-title">Eintrag hinzufügen</div>
|
||||
<div class="modal-sub">Admin · Credits Editor</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-grid">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Name</label>
|
||||
<input type="text" class="form-input" id="cf-name" placeholder="z.B. GhostHunter99">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Typ</label>
|
||||
<select class="form-select" id="cf-type">
|
||||
<option value="credit">✦ Credit</option>
|
||||
<option value="partner">⭐ Partner</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Rolle / Titel</label>
|
||||
<input type="text" class="form-input" id="cf-role" placeholder="z.B. Wiki-Autor, Community Manager">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Avatar URL (optional)</label>
|
||||
<input type="text" class="form-input" id="cf-avatar" placeholder="https://...">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row full">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Emoji / Icon (falls kein Avatar)</label>
|
||||
<input type="text" class="form-input" id="cf-emoji" placeholder="👻" maxlength="4" style="width:80px;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row full">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Beschreibung</label>
|
||||
<textarea class="form-textarea" id="cf-desc" rows="2" placeholder="Kurze Beschreibung..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Discord (optional)</label>
|
||||
<input type="text" class="form-input" id="cf-discord" placeholder="Username oder Server-Link">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Website (optional)</label>
|
||||
<input type="text" class="form-input" id="cf-website" placeholder="https://...">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row full">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Roblox Profil URL (optional)</label>
|
||||
<input type="text" class="form-input" id="cf-roblox" placeholder="https://www.roblox.com/users/...">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-footer">
|
||||
<button class="btn-sm" id="btn-cancel-credits">Abbrechen</button>
|
||||
<button class="btn-accent" id="btn-save-credit">Speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="confirm-bg" id="confirm-bg">
|
||||
<div class="confirm-box">
|
||||
<div class="confirm-title" id="confirm-title">Sicher?</div>
|
||||
@ -522,12 +643,14 @@ function setupUI() {
|
||||
if(u.isAdmin) document.getElementById('pill-admin').style.display='block';
|
||||
buildCards(); buildCheat();
|
||||
populateGhostSelect();
|
||||
loadCredits();
|
||||
if(u.isAdmin) updateAdminBadge();
|
||||
}
|
||||
|
||||
function setupGuestUI() {
|
||||
document.getElementById('guest-badge').style.display='block';
|
||||
buildCards(); buildCheat();
|
||||
loadCredits();
|
||||
}
|
||||
|
||||
/* ── AUTH ── */
|
||||
@ -639,10 +762,13 @@ function setMode(m) {
|
||||
}
|
||||
|
||||
function switchTab(t) {
|
||||
['cards','cheat','guide'].forEach((x,i)=>{
|
||||
document.getElementById('vtab-'+x).classList.toggle('active',x===t);
|
||||
document.querySelectorAll('#vnav .nav-btn')[i].classList.toggle('active',x===t);
|
||||
['cards','cheat','guide','credits'].forEach((x,i)=>{
|
||||
const el = document.getElementById('vtab-'+x);
|
||||
if(el) el.classList.toggle('active',x===t);
|
||||
const btn = document.querySelectorAll('#vnav .nav-btn')[i];
|
||||
if(btn) btn.classList.toggle('active',x===t);
|
||||
});
|
||||
if(t==='credits') renderCredits();
|
||||
}
|
||||
|
||||
/* ── ADMIN ── */
|
||||
@ -845,6 +971,147 @@ function hideLogin(){document.getElementById('login-screen').classList.remove('s
|
||||
function fmtTime(ts){if(!ts)return'—';const d=new Date(ts);return d.toLocaleDateString('de-DE',{day:'2-digit',month:'2-digit',year:'numeric'})+' '+d.toLocaleTimeString('de-DE',{hour:'2-digit',minute:'2-digit'});}
|
||||
function escH(s){return String(s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');}
|
||||
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
CREDITS
|
||||
═══════════════════════════════════════════════════ */
|
||||
let credits = [];
|
||||
let editingCreditId = null;
|
||||
|
||||
// Load from localStorage (no separate API needed — admin manages locally)
|
||||
function loadCredits() {
|
||||
try { credits = JSON.parse(localStorage.getItem('blair_credits') || '[]'); }
|
||||
catch { credits = []; }
|
||||
}
|
||||
function saveCredits() {
|
||||
localStorage.setItem('blair_credits', JSON.stringify(credits));
|
||||
}
|
||||
|
||||
function renderCredits() {
|
||||
loadCredits();
|
||||
const partners = credits.filter(c => c.type === 'partner');
|
||||
const crds = credits.filter(c => c.type === 'credit');
|
||||
|
||||
const partnerGrid = document.getElementById('credits-partners-grid');
|
||||
const creditsGrid = document.getElementById('credits-credits-grid');
|
||||
const partnerSec = document.getElementById('credits-partners-section');
|
||||
const creditsSec = document.getElementById('credits-credits-section');
|
||||
|
||||
// Partners
|
||||
if (!partners.length) {
|
||||
partnerGrid.innerHTML = '<div class="credits-empty">Noch keine Partner eingetragen.</div>';
|
||||
} else {
|
||||
partnerGrid.innerHTML = '';
|
||||
partners.forEach(c => partnerGrid.appendChild(buildCreditCard(c)));
|
||||
}
|
||||
|
||||
// Credits
|
||||
if (!crds.length) {
|
||||
creditsGrid.innerHTML = '<div class="credits-empty">Noch keine Credits eingetragen.</div>';
|
||||
} else {
|
||||
creditsGrid.innerHTML = '';
|
||||
crds.forEach(c => creditsGrid.appendChild(buildCreditCard(c)));
|
||||
}
|
||||
|
||||
// Show add button for admins
|
||||
const adminBtn = document.getElementById('credits-admin-btn');
|
||||
if (adminBtn) adminBtn.style.display = (currentUser?.isAdmin) ? 'block' : 'none';
|
||||
}
|
||||
|
||||
function buildCreditCard(c) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'credit-card' + (c.type === 'partner' ? ' partner' : '');
|
||||
|
||||
const avatarHtml = c.avatar
|
||||
? `<img class="credit-avatar" src="${escH(c.avatar)}" alt="${escH(c.name)}" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'">`
|
||||
+ `<div class="credit-avatar-placeholder" style="display:none">${c.emoji || '👤'}</div>`
|
||||
: `<div class="credit-avatar-placeholder">${c.emoji || '👤'}</div>`;
|
||||
|
||||
const linksHtml = [
|
||||
c.discord ? `<a class="credit-link discord" href="${c.discord.startsWith('http') ? escH(c.discord) : '#'}" target="_blank" rel="noopener">Discord</a>` : '',
|
||||
c.website ? `<a class="credit-link website" href="${escH(c.website)}" target="_blank" rel="noopener">Website</a>` : '',
|
||||
c.roblox ? `<a class="credit-link roblox" href="${escH(c.roblox)}" target="_blank" rel="noopener">Roblox</a>` : '',
|
||||
].filter(Boolean).join('');
|
||||
|
||||
const adminBtns = currentUser?.isAdmin
|
||||
? `<div style="display:flex;gap:6px;margin-top:10px;">
|
||||
<button class="icon-btn ibe" onclick="openCreditForm('${c.id}')" title="Bearbeiten">✏</button>
|
||||
<button class="icon-btn ibd" onclick="confirmDeleteCredit('${c.id}')" title="Löschen">🗑</button>
|
||||
</div>` : '';
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="credit-header">
|
||||
${avatarHtml}
|
||||
<div style="flex:1;min-width:0;">
|
||||
<div class="credit-name">${escH(c.name)}</div>
|
||||
<div class="credit-role ${c.type === 'partner' ? 'partner-role' : ''}">${escH(c.role || '')}</div>
|
||||
</div>
|
||||
${c.type === 'partner' ? '<span class="partner-badge">Partner</span>' : ''}
|
||||
</div>
|
||||
${c.desc ? `<div class="credit-desc">${escH(c.desc)}</div>` : ''}
|
||||
${linksHtml ? `<div class="credit-links">${linksHtml}</div>` : ''}
|
||||
${adminBtns}`;
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
function openCreditForm(id) {
|
||||
const c = id ? credits.find(x => x.id === id) : null;
|
||||
editingCreditId = id || null;
|
||||
document.getElementById('cm-title').textContent = c ? `Bearbeiten: ${c.name}` : 'Eintrag hinzufügen';
|
||||
document.getElementById('cf-name').value = c?.name || '';
|
||||
document.getElementById('cf-type').value = c?.type || 'credit';
|
||||
document.getElementById('cf-role').value = c?.role || '';
|
||||
document.getElementById('cf-avatar').value = c?.avatar || '';
|
||||
document.getElementById('cf-emoji').value = c?.emoji || '';
|
||||
document.getElementById('cf-desc').value = c?.desc || '';
|
||||
document.getElementById('cf-discord').value = c?.discord || '';
|
||||
document.getElementById('cf-website').value = c?.website || '';
|
||||
document.getElementById('cf-roblox').value = c?.roblox || '';
|
||||
openModal('credits-modal');
|
||||
}
|
||||
|
||||
function saveCreditEntry() {
|
||||
const name = document.getElementById('cf-name').value.trim();
|
||||
if (!name) { toast("Name darf nicht leer sein!", "danger"); return; }
|
||||
|
||||
const entry = {
|
||||
id: editingCreditId || 'c_' + Date.now().toString(36),
|
||||
name,
|
||||
type: document.getElementById('cf-type').value,
|
||||
role: document.getElementById('cf-role').value.trim(),
|
||||
avatar: document.getElementById('cf-avatar').value.trim(),
|
||||
emoji: document.getElementById('cf-emoji').value.trim() || '👤',
|
||||
desc: document.getElementById('cf-desc').value.trim(),
|
||||
discord: document.getElementById('cf-discord').value.trim(),
|
||||
website: document.getElementById('cf-website').value.trim(),
|
||||
roblox: document.getElementById('cf-roblox').value.trim(),
|
||||
};
|
||||
|
||||
if (editingCreditId) {
|
||||
const idx = credits.findIndex(c => c.id === editingCreditId);
|
||||
if (idx > -1) credits[idx] = entry;
|
||||
} else {
|
||||
credits.push(entry);
|
||||
}
|
||||
|
||||
saveCredits();
|
||||
closeModal('credits-modal');
|
||||
renderCredits();
|
||||
toast(`✓ ${name} gespeichert!`, "success");
|
||||
}
|
||||
|
||||
function confirmDeleteCredit(id) {
|
||||
const c = credits.find(x => x.id === id);
|
||||
if (!c) return;
|
||||
showConfirm(`${c.name} entfernen?`, 'Dieser Eintrag wird aus Credits & Partner entfernt.', () => {
|
||||
credits = credits.filter(x => x.id !== id);
|
||||
saveCredits();
|
||||
renderCredits();
|
||||
toast(`${c.name} entfernt.`, "danger");
|
||||
});
|
||||
}
|
||||
|
||||
/* ── BOOT ── */
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
@ -912,6 +1179,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const guestBtn = document.getElementById('guest-btn');
|
||||
if (guestBtn) guestBtn.addEventListener('click', guestMode);
|
||||
|
||||
// ── Credits ──
|
||||
document.getElementById('close-credits-modal').addEventListener('click', () => closeModal('credits-modal'));
|
||||
document.getElementById('btn-cancel-credits').addEventListener('click', () => closeModal('credits-modal'));
|
||||
document.getElementById('btn-save-credit').addEventListener('click', saveCreditEntry);
|
||||
document.getElementById('credits-modal').addEventListener('click', (e) => {
|
||||
if (e.target === document.getElementById('credits-modal')) closeModal('credits-modal');
|
||||
});
|
||||
const addCreditBtn = document.getElementById('btn-add-credit');
|
||||
if (addCreditBtn) addCreditBtn.addEventListener('click', () => openCreditForm(null));
|
||||
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user