PHASE 1 — Sofort-Cleanup ohne Risiko: - Neue Datei utilities.css mit ~25 Klassen für häufige Kombinationen: * text-xs-muted, text-xs-secondary, text-sm-muted, text-sm-secondary * flex-gap-2/3, flex-col-gap-2/3/4, flex-center-gap-1/2/3 * flex-between, flex-1-min, mb-1/3, mt-1/3 * icon-xs/sm/md/lg, label-block, caption - index.html bindet utilities.css ein - mb-3/mt-3 ergänzt (waren in design-system.css unvollständig) PHASE 2 — .by-tab Modifier für Vereinheitlichung: - .by-tabs.grid (mit --tab-cols Variable für Admin/Health/etc.) - .by-tabs.sticky (Desktop vertikale Tabs für Admin) - .by-tabs.wrap (Zuchthunde, flex-wrap statt scroll) - .by-tabs.separated (Sitting, mit eigenem Hintergrund + Border) PHASE 3 — Inline-Style → Klassen-Migration (Python-Script): - 948 Inline-Styles entfernt (5101 → 4153, -18%) - 962 Migrationen über 47 Page-Dateien - Top-Treffer: admin.js (180), health.js (67), dog-profile.js (67), litters.js (62), settings.js (61), zuchthunde.js (51) - Patterns: text-muted, text-secondary, text-danger, text-xs-muted, text-sm-muted, grid-2 (Duplikat-Bug behoben!), flex-col-gap-3, p-3/4, mb-2/3/4, hidden, w-full, flex-1, ... - Bewahrt bestehende class-Attribute (mergt korrekt) Alle 19 Tests grün. Kein visueller Diff erwartet (gleiche Property-Werte).
1431 lines
74 KiB
JavaScript
1431 lines
74 KiB
JavaScript
/* BAN YARO — Social Media Manager (Luna-Coach) */
|
|
window.Page_social = (() => {
|
|
|
|
let _el, _state, _breeds = [], _activeTab = 'idee', _stats = null;
|
|
|
|
const _SL = { idea:'Idee', draft:'Entwurf', scheduled:'Geplant',
|
|
published:'Veröffentlicht', archived:'Archiviert' };
|
|
const _SC = { idea:'var(--c-text-muted)', draft:'var(--c-warning)',
|
|
scheduled:'var(--c-primary)', published:'var(--c-success)',
|
|
archived:'var(--c-text-muted)' };
|
|
const _FL = { reel:'<svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#film-slate"></use></svg> Reel', story:'⭕ Story', post:'🖼 Post', carousel:'🎠 Carousel' };
|
|
const _PL = { tiktok:'<svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#music-notes"></use></svg> TikTok', instagram:'<svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#camera"></use></svg> Instagram', both:'<svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#globe"></use></svg> Beide' };
|
|
|
|
// Luna-Wartebotschaften (alle 10s rotierend)
|
|
const _LUNA_MSGS = [
|
|
['🌙','Ich überlege…'],
|
|
['🤔','Einen Moment noch…'],
|
|
['💡','Mir ist da was eingefallen…'],
|
|
['✍️','Ich arbeite das aus…'],
|
|
['⏳','Kleinen Moment noch…'],
|
|
['<svg class="ph-icon" aria-hidden="true" style="width:16px;height:16px"><use href="/icons/phosphor.svg#sparkle"></use></svg>','Gleich hab ich es…'],
|
|
['🎯','Ich feile noch etwas dran…'],
|
|
['🐾','Fast fertig für dich…'],
|
|
];
|
|
|
|
let _diversity = null, _unusedBreeds = [];
|
|
|
|
async function init(el, state) {
|
|
_el = el; _state = state;
|
|
[_breeds, _stats, _diversity, _unusedBreeds] = await Promise.all([
|
|
API.get('/social/breeds').catch(() => []),
|
|
API.get('/social/stats').catch(() => null),
|
|
API.get('/social/diversity').catch(() => null),
|
|
API.get('/social/unused-breeds?limit=6').catch(() => []),
|
|
]);
|
|
_render();
|
|
}
|
|
function refresh() { if (_el) init(_el, _state); }
|
|
|
|
// ---------------------------------------------------------------
|
|
// HAUPT-RENDER
|
|
// ---------------------------------------------------------------
|
|
function _render() {
|
|
const lvlBar = _stats ? _levelBar(_stats) : '';
|
|
_el.innerHTML = `
|
|
<div style="width:100%;max-width:860px;margin:0 auto">
|
|
<div style="background:var(--c-surface);border-radius:var(--radius-lg);
|
|
box-shadow:var(--shadow-sm);padding:var(--space-4);
|
|
margin-bottom:var(--space-4)">
|
|
<div style="display:flex;align-items:center;gap:var(--space-3);margin-bottom:var(--space-3)">
|
|
<span style="font-size:1.6em">📱</span>
|
|
<div class="flex-1-min">
|
|
<div style="font-size:var(--text-base);font-weight:700">Social Media</div>
|
|
<div style="font-size:11px;color:var(--c-text-muted)">Luna ist dein KI-Coach</div>
|
|
</div>
|
|
${_stats ? `<div style="text-align:right;flex-shrink:0">
|
|
<div style="font-size:var(--text-xs);font-weight:700">${_stats.level}</div>
|
|
<div style="font-size:10px;color:var(--c-text-muted)">${_stats.xp} XP</div>
|
|
</div>` : ''}
|
|
</div>
|
|
${lvlBar}
|
|
</div>
|
|
|
|
<!-- Tabs -->
|
|
<div style="display:flex;border-bottom:2px solid var(--c-border);
|
|
margin-bottom:var(--space-4)">
|
|
${[['idee','<svg class="ph-icon" aria-hidden="true" style="width:16px;height:16px"><use href="/icons/phosphor.svg#sparkle"></use></svg> Ideen'],['archiv','📂 Archiv'],['bewerten','🔍 Prüfen']].map(([t,l]) => `
|
|
<button class="sm-tab" data-tab="${t}" style="flex:1;padding:10px 4px;border:none;
|
|
background:none;cursor:pointer;font-size:13px;white-space:nowrap;
|
|
border-bottom:3px solid ${_activeTab===t?'var(--c-primary)':'transparent'};
|
|
margin-bottom:-2px;font-weight:${_activeTab===t?'700':'400'};
|
|
color:${_activeTab===t?'var(--c-primary)':'var(--c-text-secondary)'}">
|
|
${l}</button>`).join('')}
|
|
</div>
|
|
<div id="sm-content"></div>
|
|
</div>`;
|
|
|
|
_el.querySelectorAll('.sm-tab').forEach(b =>
|
|
b.addEventListener('click', () => { _activeTab = b.dataset.tab; _render(); }));
|
|
|
|
const c = _el.querySelector('#sm-content');
|
|
if (_activeTab === 'idee') _renderIdee(c);
|
|
if (_activeTab === 'archiv') _renderArchiv(c);
|
|
if (_activeTab === 'bewerten') _renderBewerten(c);
|
|
}
|
|
|
|
function _levelBar(s) {
|
|
if (!s.xp_next) return '';
|
|
const pct = Math.min(100, Math.round(
|
|
((s.xp - s.xp_current_min) / (s.xp_next - s.xp_current_min)) * 100));
|
|
return `<div>
|
|
<div style="display:flex;justify-content:space-between;font-size:12px;
|
|
color:var(--c-text);margin-bottom:6px;font-weight:500">
|
|
<span>${s.level}</span>
|
|
${s.next_level ? `<span class="text-secondary">${s.xp_next - s.xp} XP bis ${s.next_level}</span>` : ''}
|
|
</div>
|
|
<div style="background:var(--c-surface-2);border-radius:var(--radius-full);height:8px;overflow:hidden">
|
|
<div style="height:100%;background:linear-gradient(90deg,var(--c-primary),var(--c-primary-light));
|
|
width:${pct}%;border-radius:var(--radius-full);transition:width .5s"></div>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// IDEEN-TAB
|
|
// ---------------------------------------------------------------
|
|
function _renderIdee(el) {
|
|
let selPlatform = 'both', selFormat = 'post', uploadedMediaUrl = null;
|
|
|
|
el.innerHTML = `
|
|
<!-- Luna -->
|
|
<div style="background:var(--c-surface);border-radius:var(--radius-lg);
|
|
box-shadow:var(--shadow-sm);padding:var(--space-4);
|
|
margin-bottom:var(--space-4);
|
|
display:flex;gap:var(--space-3);align-items:flex-start">
|
|
<span style="font-size:2em;flex-shrink:0">🌙</span>
|
|
<div>
|
|
<div style="font-weight:700;font-size:var(--text-sm);margin-bottom:4px">
|
|
Hey, ich bin Luna 👋</div>
|
|
<div style="font-size:13px;color:var(--c-text-secondary);line-height:1.6">
|
|
Ich schlage dir Ideen vor und erkläre warum sie funktionieren.
|
|
Lern Social Media richtig — nicht nur kopieren!</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Diversitäts-Warnung -->
|
|
${_diversity?.warning ? `
|
|
<div style="background:var(--c-warning-subtle);border:1.5px solid var(--c-warning);
|
|
border-radius:var(--radius-lg);padding:var(--space-4);margin-bottom:var(--space-4)">
|
|
<div style="display:flex;gap:var(--space-3);align-items:flex-start">
|
|
<span style="font-size:1.3em;flex-shrink:0">⚠️</span>
|
|
<div>
|
|
<div style="font-weight:700;font-size:var(--text-sm);color:var(--c-warning);
|
|
margin-bottom:4px">Luna bemerkt eine Wiederholung!</div>
|
|
<div style="font-size:var(--text-sm);color:var(--c-text);line-height:1.5">
|
|
${_diversity.warning.pct}% deiner letzten Posts waren
|
|
<strong>${_diversity.warning.dominant_de}</strong>-Content.
|
|
Versuch mal was anderes, damit dein Feed abwechslungsreicher wird!</div>
|
|
${_diversity.warning.suggestions_de.length ? `
|
|
<div style="margin-top:8px;font-size:12px;color:var(--c-text-secondary)">
|
|
💡 Probier mal: <strong>${_diversity.warning.suggestions_de.join(', ')}</strong></div>` : ''}
|
|
</div>
|
|
</div>
|
|
</div>` : ''}
|
|
|
|
<!-- Vorschläge -->
|
|
<div style="background:var(--c-surface);border-radius:var(--radius-lg);
|
|
box-shadow:var(--shadow-sm);padding:var(--space-4);
|
|
margin-bottom:var(--space-4)">
|
|
<div style="display:flex;align-items:center;justify-content:space-between;
|
|
margin-bottom:var(--space-3)">
|
|
<div style="font-weight:700;font-size:var(--text-sm)">💡 Was könntest du heute posten?</div>
|
|
<button id="sm-refresh" class="btn btn-sm btn-secondary"
|
|
style="font-size:11px;padding:4px 12px;min-height:32px;
|
|
border-radius:var(--radius-full)">↻ Neue</button>
|
|
</div>
|
|
<div id="sm-suggestions">
|
|
${_lunaThinking('Klein...')}
|
|
</div>
|
|
</div>
|
|
|
|
<div style="display:flex;align-items:center;gap:var(--space-3);
|
|
margin-bottom:var(--space-3)">
|
|
<div style="flex:1;height:1px;background:var(--c-border)"></div>
|
|
<div style="font-weight:600;font-size:var(--text-sm);color:var(--c-text-muted);
|
|
white-space:nowrap">✏️ Oder eigenes Thema</div>
|
|
<div style="flex:1;height:1px;background:var(--c-border)"></div>
|
|
</div>
|
|
|
|
<!-- Formular -->
|
|
<div style="background:var(--c-surface);border-radius:var(--radius-lg);
|
|
box-shadow:var(--shadow-sm);padding:var(--space-4)">
|
|
<!-- Plattform -->
|
|
<div class="mb-4">
|
|
<div class="sm-label">Plattform</div>
|
|
<div class="flex-gap-2">
|
|
${['both','instagram','tiktok'].map((p,i) => `
|
|
<button class="btn btn-sm sm-plat ${i===0?'btn-primary':'btn-secondary'}"
|
|
data-p="${p}" style="flex:1;min-height:36px;font-size:12px;padding:4px 8px;
|
|
border-radius:var(--radius-full)">
|
|
${_PL[p]}</button>`).join('')}
|
|
</div>
|
|
</div>
|
|
<!-- Format -->
|
|
<div class="mb-4">
|
|
<div class="sm-label">Format</div>
|
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-2)">
|
|
${['post','reel','story','carousel'].map((f,i) => `
|
|
<button class="btn btn-sm sm-fmt ${i===0?'btn-primary':'btn-secondary'}"
|
|
data-f="${f}" style="min-height:36px;font-size:12px;padding:4px 8px;
|
|
border-radius:var(--radius-full)">
|
|
${_FL[f]}</button>`).join('')}
|
|
</div>
|
|
</div>
|
|
<!-- Thema -->
|
|
<div class="mb-4">
|
|
<div class="sm-label">Thema</div>
|
|
<textarea id="sm-topic" rows="3"
|
|
placeholder="z.B. Mein Hund beim ersten Schnee 🐾"
|
|
style="width:100%;font-size:var(--text-sm);resize:none;line-height:1.5;
|
|
background:var(--c-surface-2);color:var(--c-text);
|
|
border:1.5px solid var(--c-border);border-radius:var(--radius-md);
|
|
padding:10px 12px;box-sizing:border-box;
|
|
font-family:inherit"></textarea>
|
|
</div>
|
|
<!-- "Was jetzt?"-Banner -->
|
|
<div id="sm-next-hint" style="display:none;background:var(--c-primary);
|
|
color:#fff;border-radius:var(--radius-md);padding:10px 14px;
|
|
margin-bottom:var(--space-4);
|
|
font-size:var(--text-sm);font-weight:600;text-align:center">
|
|
<svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#check"></use></svg> Idee übernommen — prüf die Einstellungen und tippe auf <strong>Los geht's!</strong> 👇
|
|
</div>
|
|
<!-- Medien-Upload -->
|
|
<div class="mb-4">
|
|
<div class="sm-label">Foto / Video (optional)</div>
|
|
<div style="display:flex;gap:var(--space-2);flex-wrap:wrap">
|
|
<label style="cursor:pointer;flex:1">
|
|
<input type="file" id="sm-media-file" accept="image/*,video/*"
|
|
capture="environment" class="hidden">
|
|
<span class="btn btn-secondary btn-sm"
|
|
style="min-height:40px;display:flex;align-items:center;justify-content:center;
|
|
gap:6px;font-size:12px;width:100%;border-radius:var(--radius-full)">
|
|
📷 Kamera / Mediathek</span>
|
|
</label>
|
|
<label style="cursor:pointer;flex:1">
|
|
<input type="file" id="sm-media-file2" accept="image/*,video/*"
|
|
class="hidden">
|
|
<span class="btn btn-secondary btn-sm"
|
|
style="min-height:40px;display:flex;align-items:center;justify-content:center;
|
|
gap:6px;font-size:12px;width:100%;border-radius:var(--radius-full)">
|
|
📁 Dateien</span>
|
|
</label>
|
|
</div>
|
|
<div id="sm-media-preview" style="display:none;margin-top:8px;
|
|
max-width:100px;max-height:100px;border-radius:var(--radius-md);overflow:hidden"></div>
|
|
</div>
|
|
<!-- Rasse — Luna-Vorschlag + Suche -->
|
|
<div class="mb-4">
|
|
<div class="sm-label">Rasse (optional)</div>
|
|
${_unusedBreeds.length ? `
|
|
<div style="font-size:11px;color:var(--c-text-muted);margin-bottom:8px">
|
|
🌙 Noch nicht gezeigt:
|
|
<div style="display:flex;flex-wrap:wrap;gap:6px;margin-top:6px">
|
|
${_unusedBreeds.map(b =>
|
|
`<button class="sm-breed-chip" data-id="${b.id}" data-name="${_esc(b.name)}"
|
|
style="padding:5px 12px;border-radius:var(--radius-full);
|
|
border:1.5px solid var(--c-border);
|
|
background:var(--c-surface-2);color:var(--c-text);
|
|
font-size:12px;cursor:pointer;font-family:inherit;
|
|
transition:all var(--transition-fast)">
|
|
${_esc(b.name)}</button>`).join('')}
|
|
</div>
|
|
</div>` : ''}
|
|
<input id="sm-breed-search" list="sm-breed-list"
|
|
placeholder="Rasse suchen oder leer lassen…"
|
|
autocomplete="off"
|
|
style="width:100%;background:var(--c-surface-2);color:var(--c-text);
|
|
border:1.5px solid var(--c-border);border-radius:var(--radius-md);
|
|
padding:9px 12px;font-size:var(--text-sm);
|
|
font-family:inherit;box-sizing:border-box">
|
|
<datalist id="sm-breed-list">
|
|
${_breeds.map(b => `<option value="${_esc(b.name)}" data-id="${b.id}">`).join('')}
|
|
</datalist>
|
|
<input type="hidden" id="sm-breed-id">
|
|
</div>
|
|
|
|
<!-- Generier-Buttons als Cards -->
|
|
<div class="sm-label mb-3">Schnell generieren</div>
|
|
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:var(--space-2);
|
|
margin-bottom:var(--space-3)">
|
|
<button id="sm-breed-day"
|
|
style="display:flex;flex-direction:column;align-items:center;
|
|
gap:6px;padding:var(--space-3) var(--space-2);
|
|
background:var(--c-primary-subtle);border:1.5px solid var(--c-primary-light);
|
|
border-radius:var(--radius-lg);cursor:pointer;
|
|
font-family:inherit;transition:all var(--transition-fast);
|
|
box-shadow:var(--shadow-xs)">
|
|
<span style="font-size:1.8em">🐾</span>
|
|
<span style="font-size:11px;font-weight:600;color:var(--c-primary-dark);
|
|
text-align:center;line-height:1.3">Rasse des Tages</span>
|
|
${_unusedBreeds.length ? `<span style="font-size:10px;color:var(--c-text-muted)">${_unusedBreeds.length} übrig</span>` : ''}
|
|
</button>
|
|
<button id="sm-training-tip"
|
|
style="display:flex;flex-direction:column;align-items:center;
|
|
gap:6px;padding:var(--space-3) var(--space-2);
|
|
background:#f0fdf4;border:1.5px solid #86efac;
|
|
border-radius:var(--radius-lg);cursor:pointer;
|
|
font-family:inherit;transition:all var(--transition-fast);
|
|
box-shadow:var(--shadow-xs)">
|
|
<span style="font-size:1.8em">🎾</span>
|
|
<span style="font-size:11px;font-weight:600;color:#15803d;
|
|
text-align:center;line-height:1.3">Trainingstipp</span>
|
|
<span style="font-size:10px;color:#4ade80">104 Übungen</span>
|
|
</button>
|
|
<button id="sm-pflege-tip"
|
|
style="display:flex;flex-direction:column;align-items:center;
|
|
gap:6px;padding:var(--space-3) var(--space-2);
|
|
background:#faf5ff;border:1.5px solid #d8b4fe;
|
|
border-radius:var(--radius-lg);cursor:pointer;
|
|
font-family:inherit;transition:all var(--transition-fast);
|
|
box-shadow:var(--shadow-xs)">
|
|
<span style="font-size:1.8em">🛁</span>
|
|
<span style="font-size:11px;font-weight:600;color:#7c3aed;
|
|
text-align:center;line-height:1.3">Pflegetipp</span>
|
|
<span style="font-size:10px;color:#c084fc">je Rasse</span>
|
|
</button>
|
|
</div>
|
|
<button id="sm-show-exercises"
|
|
style="width:100%;min-height:36px;font-size:11px;cursor:pointer;
|
|
margin-bottom:var(--space-4);color:var(--c-text-muted);
|
|
background:none;border:1px dashed var(--c-border);
|
|
border-radius:var(--radius-md);font-family:inherit;
|
|
transition:all var(--transition-fast)">
|
|
📋 Alle 104 Übungen ansehen →
|
|
</button>
|
|
|
|
<button id="sm-gen" class="btn btn-primary"
|
|
style="width:100%;min-height:52px;font-size:var(--text-base);
|
|
border-radius:var(--radius-lg);box-shadow:var(--shadow-md)">
|
|
<svg class="ph-icon" aria-hidden="true" style="width:16px;height:16px"><use href="/icons/phosphor.svg#sparkle"></use></svg> Los geht's!
|
|
</button>
|
|
<div id="sm-gen-result" class="mt-4"></div>
|
|
</div>`;
|
|
|
|
// Platform toggle
|
|
el.querySelectorAll('.sm-plat').forEach(b => b.addEventListener('click', () => {
|
|
selPlatform = b.dataset.p;
|
|
el.querySelectorAll('.sm-plat').forEach(x =>
|
|
x.className = `btn btn-sm sm-plat ${x===b?'btn-primary':'btn-secondary'}`);
|
|
}));
|
|
el.querySelectorAll('.sm-fmt').forEach(b => b.addEventListener('click', () => {
|
|
selFormat = b.dataset.f;
|
|
el.querySelectorAll('.sm-fmt').forEach(x =>
|
|
x.className = `btn btn-sm sm-fmt ${x===b?'btn-primary':'btn-secondary'}`);
|
|
}));
|
|
|
|
// Breed-Chips
|
|
el.querySelectorAll('.sm-breed-chip').forEach(chip => {
|
|
chip.addEventListener('click', () => {
|
|
const search = el.querySelector('#sm-breed-search');
|
|
const hiddenId = el.querySelector('#sm-breed-id');
|
|
if (search) search.value = chip.dataset.name;
|
|
if (hiddenId) hiddenId.value = chip.dataset.id;
|
|
el.querySelectorAll('.sm-breed-chip').forEach(c =>
|
|
c.style.background = c === chip ? 'var(--c-primary)' : 'var(--c-surface-2)');
|
|
el.querySelectorAll('.sm-breed-chip').forEach(c =>
|
|
c.style.color = c === chip ? '#fff' : 'var(--c-text)');
|
|
});
|
|
});
|
|
|
|
// Datalist-Autocomplete → breed-id setzen
|
|
el.querySelector('#sm-breed-search')?.addEventListener('input', e => {
|
|
const val = e.target.value.trim().toLowerCase();
|
|
const match = _breeds.find(b => b.name.toLowerCase() === val);
|
|
const hiddenId = el.querySelector('#sm-breed-id');
|
|
if (hiddenId) hiddenId.value = match ? match.id : '';
|
|
});
|
|
|
|
// Media upload
|
|
function handleMedia(file) {
|
|
if (!file) return;
|
|
const preview = el.querySelector('#sm-media-preview');
|
|
const reader = new FileReader();
|
|
reader.onload = e => {
|
|
preview.style.display = '';
|
|
if (file.type.startsWith('video/')) {
|
|
preview.innerHTML = `<video src="${e.target.result}"
|
|
style="width:80px;height:80px;object-fit:cover;border-radius:8px" muted></video>`;
|
|
} else {
|
|
preview.innerHTML = `<img src="${e.target.result}"
|
|
style="width:80px;height:80px;object-fit:cover;border-radius:8px">`;
|
|
}
|
|
// Upload
|
|
const fd = new FormData();
|
|
fd.append('file', file);
|
|
fetch('/api/social/media', {
|
|
method: 'POST',
|
|
headers: {Authorization: `Bearer ${localStorage.getItem('by_token')}`},
|
|
body: fd,
|
|
}).then(r => r.json()).then(d => { uploadedMediaUrl = d.url; });
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
el.querySelector('#sm-media-file').addEventListener('change', e => handleMedia(e.target.files[0]));
|
|
el.querySelector('#sm-media-file2').addEventListener('change', e => handleMedia(e.target.files[0]));
|
|
|
|
// Vorschläge laden
|
|
async function loadSuggestions() {
|
|
const box = el.querySelector('#sm-suggestions');
|
|
box.innerHTML = `<div style="text-align:center;padding:var(--space-4)">
|
|
<div id="sg-emoji" style="font-size:1.8em;margin-bottom:8px;
|
|
animation:luna-pulse 1.2s infinite">🌙</div>
|
|
<div id="sg-text" style="font-size:var(--text-sm);color:var(--c-text)">
|
|
Luna: Ich überlege…</div>
|
|
</div>`;
|
|
let mi = 0;
|
|
const sgInt = setInterval(() => {
|
|
mi = (mi + 1) % _LUNA_MSGS.length;
|
|
const [e, t] = _LUNA_MSGS[mi];
|
|
const te = box.querySelector('#sg-text');
|
|
const ee = box.querySelector('#sg-emoji');
|
|
if (te) te.textContent = 'Luna: ' + t;
|
|
if (ee) ee.textContent = e;
|
|
}, 10000);
|
|
try {
|
|
const ideas = await API.get('/social/suggestions');
|
|
clearInterval(sgInt);
|
|
if (!ideas?.length) { box.innerHTML = '<div class="text-sm-muted">Keine Ideen erhalten.</div>'; return; }
|
|
box.innerHTML = ideas.map((idea, i) => `
|
|
<div class="card sm-idea" style="padding:12px;margin-bottom:8px;cursor:pointer;
|
|
border:2px solid transparent;transition:border-color .15s;
|
|
-webkit-tap-highlight-color:transparent" data-i="${i}">
|
|
<div style="display:flex;align-items:flex-start;gap:10px">
|
|
<span style="font-size:1.4em;flex-shrink:0;line-height:1.2">${idea.emoji||'💡'}</span>
|
|
<div class="flex-1-min">
|
|
<div style="font-weight:600;font-size:var(--text-sm);margin-bottom:4px;
|
|
line-height:1.3">${_esc(idea.thema)}</div>
|
|
<div style="font-size:11px;color:var(--c-text-secondary);margin-bottom:6px;
|
|
line-height:1.4">🎓 ${_esc(idea.warum)}</div>
|
|
<div style="display:flex;gap:6px">
|
|
<span style="font-size:10px;background:var(--c-surface-2);
|
|
padding:2px 6px;border-radius:4px">${_FL[idea.format]||idea.format}</span>
|
|
<span style="font-size:10px;background:var(--c-surface-2);
|
|
padding:2px 6px;border-radius:4px">${_PL[idea.platform]||idea.platform}</span>
|
|
</div>
|
|
</div>
|
|
<div style="display:flex;flex-direction:column;gap:5px;flex-shrink:0">
|
|
<button class="btn btn-primary btn-sm sm-use"
|
|
style="font-size:11px;padding:6px 10px;min-height:34px;white-space:nowrap"
|
|
data-thema="${_esc(idea.thema)}"
|
|
data-format="${idea.format||'post'}"
|
|
data-platform="${idea.platform||'both'}">
|
|
Nutzen →</button>
|
|
<button class="btn btn-secondary btn-sm sm-save-idea"
|
|
style="font-size:10px;padding:4px 8px;min-height:26px;white-space:nowrap"
|
|
data-thema="${_esc(idea.thema)}"
|
|
data-format="${idea.format||'post'}"
|
|
data-platform="${idea.platform||'both'}"
|
|
data-category="${_esc(idea.category||'')}">
|
|
<svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#push-pin"></use></svg> Merken</button>
|
|
</div>
|
|
</div>
|
|
</div>`).join('');
|
|
|
|
box.querySelectorAll('.sm-use').forEach(btn => {
|
|
btn.addEventListener('click', e => {
|
|
e.stopPropagation();
|
|
el.querySelector('#sm-topic').value = btn.dataset.thema;
|
|
const fb = el.querySelector(`.sm-fmt[data-f="${btn.dataset.format}"]`);
|
|
if (fb) fb.click();
|
|
const pb = el.querySelector(`.sm-plat[data-p="${btn.dataset.platform}"]`);
|
|
if (pb) pb.click();
|
|
// Banner + Scroll zu "Los geht's!"
|
|
const hint = el.querySelector('#sm-next-hint');
|
|
if (hint) hint.style.display = '';
|
|
const genBtn = el.querySelector('#sm-gen');
|
|
if (genBtn) {
|
|
setTimeout(() => {
|
|
genBtn.scrollIntoView({behavior:'smooth', block:'center'});
|
|
genBtn.style.transform = 'scale(1.04)';
|
|
setTimeout(() => genBtn.style.transform = '', 600);
|
|
}, 200);
|
|
}
|
|
});
|
|
});
|
|
box.querySelectorAll('.sm-save-idea').forEach(btn => {
|
|
btn.addEventListener('click', async e => {
|
|
e.stopPropagation();
|
|
btn.disabled = true;
|
|
btn.textContent = '…';
|
|
try {
|
|
await API.post('/social/ideas/save', {
|
|
topic: btn.dataset.thema,
|
|
format: btn.dataset.format,
|
|
platform: btn.dataset.platform,
|
|
category: btn.dataset.category || undefined,
|
|
});
|
|
btn.textContent = '<svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#check"></use></svg> Gemerkt';
|
|
btn.style.background = 'var(--c-success)';
|
|
btn.style.color = '#fff';
|
|
btn.style.borderColor = 'var(--c-success)';
|
|
} catch {
|
|
btn.textContent = '<svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#push-pin"></use></svg> Merken';
|
|
btn.disabled = false;
|
|
}
|
|
});
|
|
});
|
|
box.querySelectorAll('.sm-idea').forEach(card => {
|
|
card.addEventListener('mouseenter', () => card.style.borderColor = 'var(--c-primary)');
|
|
card.addEventListener('mouseleave', () => card.style.borderColor = 'transparent');
|
|
});
|
|
} catch {
|
|
clearInterval(sgInt);
|
|
box.innerHTML = '<div style="color:var(--c-danger);font-size:var(--text-sm)">Ideen konnten nicht geladen werden.</div>';
|
|
}
|
|
}
|
|
el.querySelector('#sm-refresh').addEventListener('click', loadSuggestions);
|
|
loadSuggestions();
|
|
|
|
// Übungs-Übersicht
|
|
el.querySelector('#sm-show-exercises').addEventListener('click', async () => {
|
|
const exercises = await API.get('/social/exercises').catch(() => []);
|
|
const cats = [...new Set(exercises.map(e => e.kategorie))];
|
|
const modal = document.createElement('div');
|
|
modal.style.cssText = `position:fixed;inset:0;background:rgba(0,0,0,.75);
|
|
z-index:9999;overflow-y:auto;padding:16px`;
|
|
modal.innerHTML = `
|
|
<div style="max-width:500px;margin:0 auto">
|
|
<div style="display:flex;justify-content:space-between;align-items:center;
|
|
margin-bottom:12px">
|
|
<div style="color:#fff;font-weight:700">
|
|
🎾 ${exercises.length} Übungen</div>
|
|
<button id="sm-ex-close" style="background:none;border:none;color:#fff;
|
|
font-size:1.5em;cursor:pointer">✕</button>
|
|
</div>
|
|
${cats.map(cat => `
|
|
<div style="margin-bottom:16px">
|
|
<div style="color:#C4843A;font-weight:700;font-size:var(--text-sm);
|
|
margin-bottom:8px">${cat}</div>
|
|
${exercises.filter(e=>e.kategorie===cat).map(e => `
|
|
<div style="background:var(--c-surface);border-radius:8px;padding:10px;
|
|
margin-bottom:6px;display:flex;align-items:center;gap:10px">
|
|
<div class="flex-1-min">
|
|
<div style="font-size:var(--text-sm);font-weight:600;color:var(--c-text)">
|
|
${_esc(e.name)}</div>
|
|
<div style="font-size:10px;color:var(--c-text-muted)">
|
|
${_esc(e.schwierigkeit||'')} · ${_esc(e.alter_ab||'')} · ${_esc(e.dauer||'')}</div>
|
|
</div>
|
|
<div style="display:flex;align-items:center;gap:6px;flex-shrink:0">
|
|
${e.posts_count > 0 ? `<span style="font-size:10px;background:#10b981;
|
|
color:#fff;padding:1px 6px;border-radius:4px"><svg class="ph-icon" aria-hidden="true" style="width:12px;height:12px"><use href="/icons/phosphor.svg#check"></use></svg> ${e.posts_count}x</span>` : ''}
|
|
<button class="btn btn-sm btn-primary sm-ex-use"
|
|
data-id="${e.exercise_id}" data-name="${_esc(e.name)}"
|
|
style="font-size:11px;padding:4px 10px;min-height:28px">
|
|
Nutzen</button>
|
|
</div>
|
|
</div>`).join('')}
|
|
</div>`).join('')}
|
|
<button id="sm-ex-close2" class="btn btn-secondary"
|
|
style="width:100%;margin-top:8px;color:#fff;
|
|
background:rgba(255,255,255,.1)">Schließen</button>
|
|
</div>`;
|
|
document.body.appendChild(modal);
|
|
['sm-ex-close','sm-ex-close2'].forEach(id =>
|
|
modal.querySelector(`#${id}`)?.addEventListener('click', () => modal.remove()));
|
|
modal.addEventListener('click', e => { if(e.target===modal) modal.remove(); });
|
|
modal.querySelectorAll('.sm-ex-use').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
// Trainingstipp für diese spezifische Übung generieren
|
|
modal.remove();
|
|
const res = el.querySelector('#sm-gen-result');
|
|
const trainBtn = el.querySelector('#sm-training-tip');
|
|
// Direkt generieren mit exercise filter via topic-Feld
|
|
el.querySelector('#sm-topic').value = btn.dataset.name;
|
|
trainBtn.scrollIntoView({behavior:'smooth', block:'center'});
|
|
const hint = el.querySelector('#sm-next-hint');
|
|
if (hint) { hint.style.display=''; hint.querySelector('strong').textContent = 'Trainingstipp generieren! 🎾'; }
|
|
});
|
|
});
|
|
});
|
|
|
|
// Trainingstipp
|
|
el.querySelector('#sm-training-tip').addEventListener('click', async () => {
|
|
const btn = el.querySelector('#sm-training-tip');
|
|
const res = el.querySelector('#sm-gen-result');
|
|
btn.disabled = true;
|
|
res.innerHTML = _lunaProgressHtml();
|
|
const interval = _startProgress(res);
|
|
try {
|
|
const data = await API.post('/social/training-tip', {});
|
|
clearInterval(interval.bar); clearInterval(interval.msg);
|
|
_progressDone(res);
|
|
await new Promise(r => setTimeout(r, 400));
|
|
const stilLabel = {tutorial:'📹 Tutorial', community:'🙋 Community', aspirational:'💪 Aspirational'}[data.stil] || '';
|
|
res.innerHTML = `
|
|
<div style="background:#f0fdf4;border:1px solid #86efac;
|
|
border-radius:var(--radius-lg);padding:var(--space-4);
|
|
margin-bottom:var(--space-3);display:flex;gap:var(--space-3);align-items:center;
|
|
box-shadow:var(--shadow-xs)">
|
|
<span style="font-size:2.2em;flex-shrink:0">🎾</span>
|
|
<div>
|
|
<div style="font-size:11px;color:#4ade80;font-weight:600;margin-bottom:2px">
|
|
Trainingstipp · ${_esc(data.exercise_kat||'')} · ${stilLabel}</div>
|
|
<div style="font-weight:700;font-size:var(--text-base);color:#15803d">
|
|
${_esc(data.exercise_name||'')}</div>
|
|
</div>
|
|
</div>
|
|
${_renderResult(data, null)}`;
|
|
_bindResultEvents(res);
|
|
Promise.all([API.get('/social/stats'), API.get('/social/diversity')])
|
|
.then(([s,d]) => { _stats = s; _diversity = d; });
|
|
} catch(e) {
|
|
clearInterval(interval.bar); clearInterval(interval.msg);
|
|
res.innerHTML = `<div style="color:var(--c-danger);padding:var(--space-3)">
|
|
😬 ${_esc(e.message||String(e))}</div>`;
|
|
} finally {
|
|
btn.disabled = false;
|
|
}
|
|
});
|
|
|
|
// Pflegetipp
|
|
el.querySelector('#sm-pflege-tip').addEventListener('click', async () => {
|
|
const btn = el.querySelector('#sm-pflege-tip');
|
|
const res = el.querySelector('#sm-gen-result');
|
|
btn.disabled = true;
|
|
res.innerHTML = _lunaProgressHtml();
|
|
const interval = _startProgress(res);
|
|
const breedId = parseInt(el.querySelector('#sm-breed-id')?.value) || null;
|
|
try {
|
|
const url = breedId ? `/social/pflege-tipp?breed_id=${breedId}` : '/social/pflege-tipp';
|
|
const data = await API.post(url, {});
|
|
clearInterval(interval.bar); clearInterval(interval.msg);
|
|
_progressDone(res);
|
|
await new Promise(r => setTimeout(r, 400));
|
|
res.innerHTML = `
|
|
<div style="background:#faf5ff;border:1px solid #d8b4fe;
|
|
border-radius:var(--radius-lg);padding:var(--space-4);
|
|
margin-bottom:var(--space-3);display:flex;gap:var(--space-3);align-items:center;
|
|
box-shadow:var(--shadow-xs)">
|
|
<span style="font-size:2.2em;flex-shrink:0">🛁</span>
|
|
<div>
|
|
<div style="font-size:11px;color:#c084fc;font-weight:600;margin-bottom:2px">
|
|
Pflegetipp · ${_esc(data.pflege_kat||'')}
|
|
${data.rasse_name ? ` · speziell für ${_esc(data.rasse_name)}` : ''}</div>
|
|
<div style="font-weight:700;font-size:var(--text-base);color:#7c3aed">
|
|
${_esc(data.pflege_titel||'')}</div>
|
|
</div>
|
|
</div>
|
|
${_renderResult(data, null)}`;
|
|
_bindResultEvents(res);
|
|
Promise.all([API.get('/social/stats'), API.get('/social/diversity')])
|
|
.then(([s,d]) => { _stats = s; _diversity = d; });
|
|
} catch(e) {
|
|
clearInterval(interval.bar); clearInterval(interval.msg);
|
|
res.innerHTML = `<div style="color:var(--c-danger);padding:var(--space-3)">
|
|
😬 ${_esc(e.message||String(e))}</div>`;
|
|
} finally { btn.disabled = false; }
|
|
});
|
|
|
|
// Rasse des Tages
|
|
el.querySelector('#sm-breed-day').addEventListener('click', async () => {
|
|
const btn = el.querySelector('#sm-breed-day');
|
|
const res = el.querySelector('#sm-gen-result');
|
|
btn.disabled = true;
|
|
res.innerHTML = _lunaProgressHtml();
|
|
const interval = _startProgress(res);
|
|
try {
|
|
const data = await API.post('/social/breed-of-day', {});
|
|
clearInterval(interval.bar); clearInterval(interval.msg);
|
|
_progressDone(res);
|
|
await new Promise(r => setTimeout(r, 400));
|
|
// Foto anzeigen wenn vorhanden
|
|
const mediaUrl = data.breed_foto || data.media_url || null;
|
|
res.innerHTML = `
|
|
<div style="background:var(--c-primary-subtle);border:1px solid var(--c-primary-light);
|
|
border-radius:var(--radius-lg);padding:var(--space-4);
|
|
margin-bottom:var(--space-3);display:flex;gap:var(--space-3);align-items:center;
|
|
box-shadow:var(--shadow-xs)">
|
|
${mediaUrl ? `<img src="${mediaUrl}"
|
|
style="width:60px;height:60px;border-radius:var(--radius-md);object-fit:cover;flex-shrink:0"
|
|
onerror="this.style.display='none'">` : '<span style="font-size:2.2em">🐶</span>'}
|
|
<div>
|
|
<div style="font-size:11px;color:var(--c-primary);font-weight:600;margin-bottom:2px">
|
|
Rasse des Tages</div>
|
|
<div style="font-weight:700;font-size:var(--text-base);color:var(--c-primary-dark)">
|
|
${_esc(data.topic?.replace('Rasse des Tages: ',''))}</div>
|
|
</div>
|
|
</div>
|
|
${_renderResult(data, mediaUrl)}`;
|
|
_bindResultEvents(res);
|
|
// unused breeds neu laden
|
|
API.get('/social/unused-breeds?limit=6').then(r => { _unusedBreeds = r; });
|
|
Promise.all([API.get('/social/stats'), API.get('/social/diversity')])
|
|
.then(([s,d]) => { _stats = s; _diversity = d; });
|
|
} catch(e) {
|
|
clearInterval(interval.bar); clearInterval(interval.msg);
|
|
res.innerHTML = `<div style="color:var(--c-danger);padding:var(--space-3)">
|
|
😬 ${_esc(e.message||String(e))}</div>`;
|
|
} finally {
|
|
btn.disabled = false;
|
|
}
|
|
});
|
|
|
|
// Generieren
|
|
el.querySelector('#sm-gen').addEventListener('click', async () => {
|
|
const topic = el.querySelector('#sm-topic').value.trim();
|
|
if (!topic) { UI.toast('Gib ein Thema ein 🐾', 'warning'); return; }
|
|
|
|
const btn = el.querySelector('#sm-gen');
|
|
const res = el.querySelector('#sm-gen-result');
|
|
btn.disabled = true;
|
|
|
|
// Luna-Progress starten
|
|
res.innerHTML = _lunaProgressHtml();
|
|
const interval = _startProgress(res);
|
|
|
|
try {
|
|
const data = await API.post('/social/generate', {
|
|
platform: selPlatform, format: selFormat,
|
|
topic,
|
|
breed_id: parseInt(el.querySelector('#sm-breed-id')?.value) || null,
|
|
});
|
|
clearInterval(interval.bar); clearInterval(interval.msg);
|
|
_progressDone(res);
|
|
await new Promise(r => setTimeout(r, 400));
|
|
res.innerHTML = _renderResult(data, uploadedMediaUrl);
|
|
_bindResultEvents(res);
|
|
el.querySelector('#sm-topic').value = '';
|
|
const hint = el.querySelector('#sm-next-hint');
|
|
if (hint) hint.style.display = 'none';
|
|
uploadedMediaUrl = null;
|
|
el.querySelector('#sm-media-preview').style.display = 'none';
|
|
// Stats + Diversity aktualisieren
|
|
Promise.all([
|
|
API.get('/social/stats'),
|
|
API.get('/social/diversity'),
|
|
]).then(([s, d]) => { _stats = s; _diversity = d; _updateLevelDisplay(); });
|
|
} catch(e) {
|
|
clearInterval(interval);
|
|
res.innerHTML = `<div style="color:var(--c-danger);padding:var(--space-3);
|
|
border-radius:8px;background:var(--c-surface-2)">
|
|
😬 Ups: ${_esc(e.message||String(e))}</div>`;
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.innerHTML = '<svg class="ph-icon" aria-hidden="true" style="width:16px;height:16px"><use href="/icons/phosphor.svg#sparkle"></use></svg> Los geht\'s!';
|
|
}
|
|
});
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// LUNA PROGRESS ANIMATION
|
|
// ---------------------------------------------------------------
|
|
function _lunaThinking(msg = 'Denkt nach…') {
|
|
return `<div style="text-align:center;padding:var(--space-4);color:var(--c-text-muted)">
|
|
<div style="font-size:1.8em;margin-bottom:8px;animation:luna-pulse 1.5s infinite">🌙</div>
|
|
<div class="text-sm">${msg}</div>
|
|
</div>`;
|
|
}
|
|
|
|
function _lunaProgressHtml() {
|
|
return `<div id="luna-prog" style="padding:var(--space-4);text-align:center">
|
|
<div id="lp-emoji" style="font-size:2.5em;margin-bottom:10px;
|
|
animation:luna-pulse 1.2s infinite">🌙</div>
|
|
<div id="lp-text" style="font-size:var(--text-sm);font-weight:600;
|
|
margin-bottom:var(--space-3);color:var(--c-text)">Luna liest dein Thema…</div>
|
|
<div style="background:var(--c-border);border-radius:8px;height:10px;
|
|
overflow:hidden;margin-bottom:6px">
|
|
<div id="lp-bar" style="height:100%;background:linear-gradient(90deg,
|
|
var(--c-primary),#f59e0b);width:0%;border-radius:8px;
|
|
transition:width .4s ease"></div>
|
|
</div>
|
|
<div id="lp-pct" style="font-size:11px;color:var(--c-text-muted)">0%</div>
|
|
</div>`;
|
|
}
|
|
|
|
function _startProgress(container) {
|
|
let elapsed = 0, msgIdx = 0;
|
|
const totalMs = 2800;
|
|
|
|
// Fortschrittsbalken: alle 250ms
|
|
const barInterval = setInterval(() => {
|
|
elapsed += 250;
|
|
const pct = Math.min(93, Math.round((elapsed / totalMs) * 100));
|
|
const bar = container.querySelector('#lp-bar');
|
|
const pctEl = container.querySelector('#lp-pct');
|
|
if (bar) bar.style.width = pct + '%';
|
|
if (pctEl) pctEl.textContent = pct + '%';
|
|
}, 250);
|
|
|
|
// Text: alle 10s rotieren
|
|
const [e0, t0] = _LUNA_MSGS[0];
|
|
const txt = container.querySelector('#lp-text');
|
|
const emoji = container.querySelector('#lp-emoji');
|
|
if (txt) txt.textContent = 'Luna: ' + t0;
|
|
if (emoji) emoji.textContent = e0;
|
|
|
|
const msgInterval = setInterval(() => {
|
|
msgIdx = (msgIdx + 1) % _LUNA_MSGS.length;
|
|
const [e, t] = _LUNA_MSGS[msgIdx];
|
|
const txt2 = container.querySelector('#lp-text');
|
|
const emoji2 = container.querySelector('#lp-emoji');
|
|
if (txt2) txt2.textContent = 'Luna: ' + t;
|
|
if (emoji2) emoji2.textContent = e;
|
|
}, 10000);
|
|
|
|
// Beide Intervalle zurückgeben
|
|
return { bar: barInterval, msg: msgInterval };
|
|
}
|
|
|
|
function _progressDone(container) {
|
|
const bar = container.querySelector('#lp-bar');
|
|
const txt = container.querySelector('#lp-text');
|
|
const emoji = container.querySelector('#lp-emoji');
|
|
const pctEl = container.querySelector('#lp-pct');
|
|
if (bar) bar.style.width = '100%';
|
|
if (pctEl) pctEl.textContent = '100%';
|
|
if (txt) txt.textContent = '🎉 Fertig!';
|
|
if (emoji) emoji.textContent = '<svg class="ph-icon" aria-hidden="true" style="width:16px;height:16px"><use href="/icons/phosphor.svg#sparkle"></use></svg>';
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// RESULT RENDER
|
|
// ---------------------------------------------------------------
|
|
function _renderResult(data, mediaUrl) {
|
|
const score = data.ai_score ? '⭐'.repeat(Math.min(data.ai_score,5)) : '';
|
|
const unsplash = data.unsplash_query
|
|
? `https://unsplash.com/s/photos/${encodeURIComponent(data.unsplash_query)}` : null;
|
|
|
|
return `
|
|
${data.coaching ? `
|
|
<div style="background:var(--c-primary-subtle);
|
|
border-radius:var(--radius-lg);padding:var(--space-4);margin-bottom:var(--space-3);
|
|
border-left:4px solid var(--c-primary)">
|
|
<div class="flex-gap-3">
|
|
<span style="font-size:1.3em;flex-shrink:0">🌙</span>
|
|
<div>
|
|
<div style="font-size:11px;font-weight:700;color:var(--c-primary);margin-bottom:4px;
|
|
text-transform:uppercase;letter-spacing:.5px">Luna sagt:</div>
|
|
<div style="font-size:var(--text-sm);line-height:1.6;color:var(--c-text)">${_esc(data.coaching)}</div>
|
|
</div>
|
|
</div>
|
|
</div>` : ''}
|
|
|
|
<div style="display:flex;align-items:center;gap:var(--space-2);
|
|
margin-bottom:var(--space-3);flex-wrap:wrap">
|
|
<span style="background:var(--c-success-subtle);color:var(--c-success);
|
|
border-radius:var(--radius-full);
|
|
padding:4px 12px;font-size:11px;font-weight:700"><svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#check"></use></svg> Gespeichert</span>
|
|
${score ? `<span style="font-size:13px">${score}</span>` : ''}
|
|
</div>
|
|
|
|
<!-- Aktions-Buttons: Primär volle Breite, Sekundär nebeneinander -->
|
|
<div class="mb-4">
|
|
<button class="btn btn-primary sm-posted-btn"
|
|
data-id="${data.id}"
|
|
style="width:100%;min-height:48px;font-size:var(--text-sm);
|
|
margin-bottom:var(--space-2);border-radius:var(--radius-lg);
|
|
background:#10b981;border-color:#10b981;box-shadow:var(--shadow-sm)">
|
|
📤 Habe ich gepostet!
|
|
</button>
|
|
<div class="flex-gap-2">
|
|
<button class="btn btn-sm btn-secondary sm-preview-btn"
|
|
data-id="${data.id}"
|
|
style="flex:1;font-size:12px;padding:6px 10px;min-height:36px;
|
|
border-radius:var(--radius-full)">
|
|
<svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#eye"></use></svg> Vorschau</button>
|
|
</div>
|
|
</div>
|
|
<div id="sm-posted-form-${data.id}" style="display:none;background:var(--c-surface-2);
|
|
border-radius:10px;padding:12px;margin-bottom:12px">
|
|
<div style="font-size:12px;font-weight:600;margin-bottom:8px">
|
|
🎉 Super! Kurze Angaben zum Post:</div>
|
|
<div style="display:grid;gap:8px">
|
|
<div>
|
|
<div class="sm-label">Datum (leer = heute)</div>
|
|
<input type="date" class="sm-post-date" data-id="${data.id}"
|
|
style="width:100%;background:var(--c-surface);color:var(--c-text);
|
|
border:1.5px solid var(--c-border);border-radius:8px;
|
|
padding:8px 12px;font-size:var(--text-sm);font-family:inherit;
|
|
box-sizing:border-box">
|
|
</div>
|
|
<div>
|
|
<div class="sm-label">Post-URL (optional)</div>
|
|
<input type="url" class="sm-post-url" data-id="${data.id}"
|
|
placeholder="https://www.instagram.com/p/..."
|
|
style="width:100%;background:var(--c-surface);color:var(--c-text);
|
|
border:1.5px solid var(--c-border);border-radius:8px;
|
|
padding:8px 12px;font-size:var(--text-sm);font-family:inherit;
|
|
box-sizing:border-box">
|
|
</div>
|
|
<button class="btn btn-primary sm-confirm-posted" data-id="${data.id}"
|
|
style="min-height:40px">
|
|
<svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#check"></use></svg> Bestätigen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
${mediaUrl ? `
|
|
<div style="background:var(--c-surface);border:1px solid var(--c-border);
|
|
border-radius:var(--radius-lg);padding:var(--space-4);
|
|
margin-bottom:var(--space-3);box-shadow:var(--shadow-xs)">
|
|
<div class="sm-label">📎 Dein Medien-Upload</div>
|
|
<img src="${mediaUrl}" style="max-width:100%;max-height:200px;
|
|
border-radius:var(--radius-md);object-fit:cover;margin-top:8px"
|
|
onerror="this.style.display='none'">
|
|
</div>` : ''}
|
|
|
|
${_resultBlock('📝 Caption', data.caption, true)}
|
|
${data.hashtags ? `
|
|
<div style="background:var(--c-surface);border:1px solid var(--c-border);
|
|
border-radius:var(--radius-lg);padding:var(--space-4);
|
|
margin-bottom:var(--space-3);box-shadow:var(--shadow-xs)">
|
|
<div class="sm-label"><svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#tag"></use></svg> Hashtags</div>
|
|
<div style="font-size:var(--text-sm);color:var(--c-primary);margin-bottom:var(--space-3);
|
|
line-height:1.9;word-break:break-word">
|
|
${data.hashtags.split(',').map(h=>`#${h.trim()}`).join(' ')}</div>
|
|
${_copyBtn(data.hashtags.split(',').map(h=>`#${h.trim()}`).join(' '))}
|
|
</div>` : ''}
|
|
${(data.hook||data.cta) ? `
|
|
<div style="background:var(--c-surface);border:1px solid var(--c-border);
|
|
border-radius:var(--radius-lg);padding:var(--space-4);
|
|
margin-bottom:var(--space-3);box-shadow:var(--shadow-xs)">
|
|
${data.hook ? `<div class="sm-label">🎣 Hook</div>
|
|
<div style="font-size:var(--text-sm);font-style:italic;margin-bottom:var(--space-3);
|
|
line-height:1.6">
|
|
"${_esc(data.hook)}"</div>` : ''}
|
|
${data.cta ? `<div class="sm-label">📣 Call-to-Action</div>
|
|
<div style="font-size:var(--text-sm);line-height:1.6">${_esc(data.cta)}</div>` : ''}
|
|
</div>` : ''}
|
|
${_resultBlock('📸 Was du filmen/fotografieren solltest', data.visual_brief, false)}
|
|
${data.script ? `
|
|
<div style="background:var(--c-surface);border:1px solid var(--c-border);
|
|
border-radius:var(--radius-lg);padding:var(--space-4);
|
|
margin-bottom:var(--space-3);box-shadow:var(--shadow-xs)">
|
|
<div class="sm-label">🎬 Video-Aufbau</div>
|
|
<div style="font-size:var(--text-sm);white-space:pre-wrap;
|
|
line-height:1.7">${_esc(data.script)}</div>
|
|
</div>` : ''}
|
|
${(data.image_prompt||data.canva_notes||unsplash) ? `
|
|
<div style="background:var(--c-surface);border:1px solid var(--c-border);
|
|
border-radius:var(--radius-lg);padding:var(--space-4);
|
|
margin-bottom:var(--space-3);box-shadow:var(--shadow-xs)">
|
|
<div class="sm-label">🛠 Wenn du kein eigenes Bild hast</div>
|
|
${data.image_prompt ? `
|
|
<div style="font-size:11px;color:var(--c-text-muted);margin-bottom:6px">
|
|
DALL-E / Midjourney:</div>
|
|
<div style="font-size:11px;background:var(--c-surface-2);padding:10px;
|
|
border-radius:var(--radius-md);font-family:monospace;word-break:break-word;
|
|
margin-bottom:var(--space-3);line-height:1.5">${_esc(data.image_prompt)}</div>
|
|
${_copyBtn(data.image_prompt)}` : ''}
|
|
${data.canva_notes ? `
|
|
<div style="font-size:11px;color:var(--c-text-muted);margin:var(--space-3) 0 6px">Canva:</div>
|
|
<div style="font-size:var(--text-sm);margin-bottom:var(--space-3);
|
|
line-height:1.6">${_esc(data.canva_notes)}</div>` : ''}
|
|
${unsplash ? `<a href="${unsplash}" target="_blank" rel="noopener"
|
|
style="font-size:var(--text-sm);color:var(--c-primary);display:inline-block">
|
|
🔍 Kostenlose Fotos auf Unsplash →</a>` : ''}
|
|
</div>` : ''}`;
|
|
}
|
|
|
|
function _resultBlock(label, text, copyable) {
|
|
if (!text) return '';
|
|
return `<div style="background:var(--c-surface);border:1px solid var(--c-border);
|
|
border-radius:var(--radius-lg);padding:var(--space-4);
|
|
margin-bottom:var(--space-3);box-shadow:var(--shadow-xs)">
|
|
<div class="sm-label">${label}</div>
|
|
<div style="font-size:var(--text-sm);white-space:pre-wrap;line-height:1.7;
|
|
margin-bottom:${copyable?'var(--space-3)':'0'}">${_esc(text)}</div>
|
|
${copyable ? _copyBtn(text) : ''}
|
|
</div>`;
|
|
}
|
|
|
|
function _copyBtn(text) {
|
|
return `<button class="btn btn-sm btn-secondary sm-copy"
|
|
data-copy="${_esc(text)}"
|
|
style="font-size:11px;padding:5px 14px;min-height:32px;
|
|
border-radius:var(--radius-full)">
|
|
📋 Kopieren</button>`;
|
|
}
|
|
|
|
function _bindResultEvents(el) {
|
|
el.querySelectorAll('.sm-copy').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const text = btn.getAttribute('data-copy')
|
|
.replace(/&/g,'&').replace(/</g,'<')
|
|
.replace(/>/g,'>').replace(/"/g,'"');
|
|
navigator.clipboard?.writeText(text).then(() => {
|
|
btn.textContent = '<svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#check"></use></svg> Kopiert!';
|
|
setTimeout(() => btn.textContent = '📋 Kopieren', 2000);
|
|
});
|
|
});
|
|
});
|
|
el.querySelectorAll('.sm-preview-btn').forEach(btn => {
|
|
btn.addEventListener('click', async () => {
|
|
const id = parseInt(btn.dataset.id);
|
|
const contents = await API.get('/social/content').catch(() => []);
|
|
const item = contents.find(c => c.id === id);
|
|
if (item) _showPreview(item);
|
|
});
|
|
});
|
|
// "Habe ich gepostet!" — Formular einblenden
|
|
el.querySelectorAll('.sm-posted-btn').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const form = el.querySelector(`#sm-posted-form-${btn.dataset.id}`);
|
|
if (form) {
|
|
form.style.display = form.style.display === 'none' ? '' : 'none';
|
|
// Heute als Default-Datum
|
|
const dateInput = form.querySelector('.sm-post-date');
|
|
if (dateInput && !dateInput.value) {
|
|
dateInput.value = new Date().toISOString().slice(0,10);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
// Bestätigen
|
|
el.querySelectorAll('.sm-confirm-posted').forEach(btn => {
|
|
btn.addEventListener('click', async () => {
|
|
const id = parseInt(btn.dataset.id);
|
|
const form = el.querySelector(`#sm-posted-form-${id}`);
|
|
const date = form?.querySelector('.sm-post-date')?.value
|
|
|| new Date().toISOString().slice(0,16);
|
|
const url = form?.querySelector('.sm-post-url')?.value || null;
|
|
btn.disabled = true;
|
|
btn.textContent = '…';
|
|
await API.patch(`/social/content/${id}`, {
|
|
status: 'published',
|
|
published_at: date,
|
|
post_url: url || undefined,
|
|
});
|
|
// Form durch Bestätigung ersetzen
|
|
if (form) form.innerHTML = `
|
|
<div style="text-align:center;padding:8px;color:var(--c-success);
|
|
font-weight:600;font-size:var(--text-sm)">
|
|
🎉 Super! Post als veröffentlicht markiert.
|
|
${url ? `<br><a href="${_esc(url)}" target="_blank" rel="noopener"
|
|
style="font-size:11px;color:var(--c-primary)">Post ansehen →</a>` : ''}
|
|
</div>`;
|
|
// Stats aktualisieren
|
|
API.get('/social/stats').then(s => { _stats = s; });
|
|
});
|
|
});
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// POST-VORSCHAU
|
|
// ---------------------------------------------------------------
|
|
function _showPreview(item) {
|
|
const modal = document.createElement('div');
|
|
modal.style.cssText = `position:fixed;inset:0;background:rgba(0,0,0,.7);
|
|
z-index:9999;display:flex;align-items:center;justify-content:center;padding:16px;
|
|
overflow-y:auto`;
|
|
|
|
const isReel = item.format === 'reel';
|
|
const w = isReel ? '220px' : '280px';
|
|
const h = isReel ? '390px' : '300px';
|
|
|
|
modal.innerHTML = `
|
|
<div style="max-width:320px;width:100%">
|
|
<div style="display:flex;justify-content:space-between;align-items:center;
|
|
margin-bottom:12px">
|
|
<div style="color:#fff;font-weight:700;font-size:var(--text-sm)">
|
|
${item.platform==='tiktok'?'🎵 TikTok':'📸 Instagram'} Vorschau</div>
|
|
<button id="sm-close-preview" style="background:none;border:none;color:#fff;
|
|
font-size:1.5em;cursor:pointer;padding:4px">✕</button>
|
|
</div>
|
|
|
|
<!-- Instagram-Mockup -->
|
|
<div style="background:#fff;border-radius:12px;overflow:hidden;
|
|
box-shadow:0 8px 32px rgba(0,0,0,.4)">
|
|
<!-- Header -->
|
|
<div style="display:flex;align-items:center;gap:8px;padding:10px 12px">
|
|
<div style="width:30px;height:30px;border-radius:50%;background:#C4843A;
|
|
display:flex;align-items:center;justify-content:center;
|
|
font-size:16px">🐾</div>
|
|
<div>
|
|
<div style="font-size:12px;font-weight:700;color:#000">banyaro.app</div>
|
|
<div style="font-size:10px;color:#666">Jetzt</div>
|
|
</div>
|
|
<div style="margin-left:auto;font-size:18px;color:#000">···</div>
|
|
</div>
|
|
<!-- Bild -->
|
|
<div style="width:100%;height:${h};background:linear-gradient(135deg,#f3e8d0,#e8d5b0);
|
|
display:flex;align-items:center;justify-content:center;
|
|
font-size:3em;color:#8B6914">🐶</div>
|
|
<!-- Actions -->
|
|
<div style="padding:8px 12px">
|
|
<div style="display:flex;gap:14px;margin-bottom:6px;font-size:20px">
|
|
<span><svg class="ph-icon" aria-hidden="true" style="width:20px;height:20px"><use href="/icons/phosphor.svg#heart-straight"></use></svg></span><span><svg class="ph-icon" aria-hidden="true" style="width:20px;height:20px"><use href="/icons/phosphor.svg#chat-circle"></use></svg></span><span><svg class="ph-icon" aria-hidden="true" style="width:20px;height:20px"><use href="/icons/phosphor.svg#arrow-square-out"></use></svg></span>
|
|
<span style="margin-left:auto">🔖</span>
|
|
</div>
|
|
<div style="font-size:11px;font-weight:700;color:#000;margin-bottom:4px">
|
|
banyaro.app</div>
|
|
<div style="font-size:11px;color:#000;line-height:1.4;
|
|
display:-webkit-box;-webkit-line-clamp:3;
|
|
-webkit-box-orient:vertical;overflow:hidden">
|
|
${_esc((item.caption||'').substring(0,150))}${(item.caption||'').length>150?'…':''}</div>
|
|
${item.hashtags ? `<div style="font-size:10px;color:#00376b;margin-top:4px;
|
|
word-break:break-word;display:-webkit-box;-webkit-line-clamp:2;
|
|
-webkit-box-orient:vertical;overflow:hidden">
|
|
${item.hashtags.split(',').slice(0,5).map(h=>`#${h.trim()}`).join(' ')}</div>` : ''}
|
|
</div>
|
|
</div>
|
|
|
|
<button id="sm-close-preview2" class="btn btn-secondary"
|
|
style="width:100%;margin-top:12px;color:#fff;border-color:#fff66;
|
|
background:rgba(255,255,255,.1)">Schließen</button>
|
|
</div>`;
|
|
|
|
document.body.appendChild(modal);
|
|
['sm-close-preview','sm-close-preview2'].forEach(id => {
|
|
modal.querySelector(`#${id}`)?.addEventListener('click', () => modal.remove());
|
|
});
|
|
modal.addEventListener('click', e => { if (e.target===modal) modal.remove(); });
|
|
}
|
|
|
|
function _updateLevelDisplay() {
|
|
if (!_stats || !_el) return;
|
|
const lvlEl = _el.querySelector('[data-level]');
|
|
if (lvlEl) lvlEl.textContent = _stats.level;
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// ARCHIV
|
|
// ---------------------------------------------------------------
|
|
async function _renderArchiv(el) {
|
|
el.innerHTML = _lunaThinking('Lädt…');
|
|
let filter = 'alle';
|
|
const fLabel = {alle:'Alle',idea:'Ideen',draft:'Entwürfe',
|
|
scheduled:'Geplant',published:'Veröffentlicht',archived:'Archiviert'};
|
|
|
|
async function load(f) {
|
|
filter = f;
|
|
const url = f==='alle' ? '/social/content' : `/social/content?status=${f}`;
|
|
const [items, allItems] = await Promise.all([
|
|
API.get(url).catch(() => []),
|
|
f !== 'alle' ? API.get('/social/content').catch(() => []) : Promise.resolve(null),
|
|
]);
|
|
const pending = (allItems || items).filter(c =>
|
|
c.status === 'idea' || c.status === 'draft').length;
|
|
render(items, pending);
|
|
}
|
|
|
|
function render(items, pending) {
|
|
el.innerHTML = `
|
|
${pending > 0 ? `
|
|
<div style="background:var(--c-surface-2);border:1.5px solid var(--c-warning);
|
|
border-radius:10px;padding:10px 12px;margin-bottom:12px;
|
|
display:flex;align-items:center;gap:10px;font-size:var(--text-sm)">
|
|
<span style="font-size:1.3em;flex-shrink:0">⏳</span>
|
|
<div>
|
|
<div style="font-weight:600;color:var(--c-warning)">${pending} Post${pending>1?'s':''} warten auf Bestätigung</div>
|
|
<div style="font-size:11px;color:var(--c-text-secondary)">
|
|
Tippe auf 📤 wenn du einen Post abgesetzt hast — so lernt Luna was wirklich live ging.</div>
|
|
</div>
|
|
</div>` : ''}
|
|
<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:var(--space-4)">
|
|
${['alle','idea','draft','scheduled','published','archived'].map(s => `
|
|
<button class="btn btn-sm ${filter===s?'btn-primary':'btn-secondary'}"
|
|
data-f="${s}" style="padding:4px 12px;font-size:11px;min-height:30px;
|
|
border-radius:var(--radius-full)">
|
|
${fLabel[s]}</button>`).join('')}
|
|
</div>
|
|
${!items.length
|
|
? UI.emptyState({icon:'camera',title:'Noch nichts hier',
|
|
text:'Geh zu "<svg class="ph-icon" aria-hidden="true" style="width:16px;height:16px"><use href="/icons/phosphor.svg#sparkle"></use></svg> Ideen" und erstelle deinen ersten Post!'})
|
|
: items.map(c => `
|
|
<div class="card" style="padding:12px;margin-bottom:10px">
|
|
<div style="display:flex;align-items:flex-start;gap:10px">
|
|
<div class="flex-1-min">
|
|
<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:4px;
|
|
align-items:center">
|
|
<span style="font-size:11px;padding:2px 6px;border-radius:4px;
|
|
background:var(--c-surface-2);color:${_SC[c.status]};
|
|
font-weight:600">${_SL[c.status]||c.status}</span>
|
|
<span style="font-size:10px;color:var(--c-text-muted)">${c.created_at?.slice(0,10)||''}</span>
|
|
${c.ai_score ? `<span style="font-size:10px">${'⭐'.repeat(c.ai_score)}</span>` : ''}
|
|
</div>
|
|
<div style="font-weight:600;font-size:var(--text-sm);margin-bottom:3px;
|
|
line-height:1.3">${_esc(c.topic)}</div>
|
|
${c.hook ? `<div style="font-size:11px;color:var(--c-text-secondary);
|
|
font-style:italic;margin-bottom:2px">🎣 ${_esc(c.hook)}</div>` : ''}
|
|
${c.post_url
|
|
? `<a href="${_esc(c.post_url)}" target="_blank" rel="noopener"
|
|
style="font-size:10px;color:var(--c-primary);display:inline-flex;
|
|
align-items:center;gap:3px;margin-top:2px">
|
|
<svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#link-simple"></use></svg> Post ansehen</a>`
|
|
: c.status === 'published'
|
|
? `<button class="sm-add-url" data-id="${c.id}"
|
|
style="font-size:10px;color:var(--c-text-muted);background:none;
|
|
border:none;cursor:pointer;padding:0;margin-top:2px;
|
|
text-align:left;text-decoration:underline">
|
|
+ Link eintragen</button>`
|
|
: ''}
|
|
</div>
|
|
<div style="display:flex;flex-direction:column;gap:4px;flex-shrink:0">
|
|
${c.status !== 'published' ? `
|
|
<button class="btn btn-sm sm-quick-post" data-id="${c.id}"
|
|
style="padding:3px 8px;font-size:11px;min-height:28px;
|
|
background:#10b981;border:1px solid #10b981;
|
|
color:#fff;border-radius:6px;cursor:pointer">
|
|
📤</button>` : `
|
|
<div style="text-align:center;font-size:18px;padding:3px 8px"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#check-circle"></use></svg></div>`}
|
|
<button class="btn btn-sm btn-secondary sm-exp"
|
|
data-id="${c.id}" style="padding:3px 8px;font-size:11px;min-height:28px">
|
|
Details</button>
|
|
<button class="btn btn-sm btn-secondary sm-prev-arch"
|
|
data-id="${c.id}" style="padding:3px 8px;font-size:11px;min-height:28px">
|
|
<svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#eye"></use></svg></button>
|
|
<button class="btn btn-sm btn-secondary sm-del"
|
|
data-id="${c.id}" style="padding:3px 8px;font-size:11px;
|
|
min-height:28px;color:var(--c-danger)">✕</button>
|
|
</div>
|
|
</div>
|
|
<div id="sm-d-${c.id}" style="display:none;margin-top:10px;
|
|
border-top:1px solid var(--c-border);padding-top:10px">
|
|
${c.coaching ? `<div style="background:var(--c-surface-2);border-radius:8px;
|
|
padding:10px;margin-bottom:10px;font-size:11px;line-height:1.5">
|
|
🌙 ${_esc(c.coaching)}</div>` : ''}
|
|
${c.caption ? `<div style="font-size:11px;color:var(--c-text-muted);margin-bottom:2px">Caption:</div>
|
|
<div style="font-size:var(--text-sm);white-space:pre-wrap;line-height:1.5;
|
|
margin-bottom:8px">${_esc(c.caption)}</div>
|
|
${_copyBtn(c.caption)}` : ''}
|
|
${c.hashtags ? `<div style="font-size:11px;color:var(--c-primary);margin-top:8px;
|
|
word-break:break-word">
|
|
${c.hashtags.split(',').map(h=>`#${h.trim()}`).join(' ')}</div>
|
|
${_copyBtn(c.hashtags.split(',').map(h=>`#${h.trim()}`).join(' '))}` : ''}
|
|
<div style="display:flex;gap:6px;flex-wrap:wrap;margin-top:10px">
|
|
${({idea:['draft','archived'],draft:['scheduled','archived'],
|
|
scheduled:['published','draft'],published:['archived'],
|
|
archived:['idea']}[c.status]||[]).map(s => `
|
|
<button class="btn btn-sm btn-secondary sm-sts"
|
|
data-id="${c.id}" data-s="${s}"
|
|
style="font-size:11px;padding:3px 8px;min-height:28px">
|
|
→ ${fLabel[s]||s}</button>`).join('')}
|
|
</div>
|
|
</div>
|
|
</div>`).join('')}`;
|
|
|
|
el.querySelectorAll('[data-f]').forEach(b => b.addEventListener('click', () => load(b.dataset.f)));
|
|
|
|
// Quick-Post: Inline-Form statt prompt()
|
|
el.querySelectorAll('.sm-quick-post').forEach(b => b.addEventListener('click', () => {
|
|
const id = b.dataset.id;
|
|
UI.modal.open({
|
|
title: '📤 Als gepostet markieren',
|
|
body: `
|
|
<div class="form-group">
|
|
<label class="form-label">Datum</label>
|
|
<input class="form-control" type="date" id="qp-date-${id}"
|
|
value="${new Date().toISOString().slice(0,10)}">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Post-Link <span class="text-muted">(optional)</span></label>
|
|
<input class="form-control" type="url" id="qp-url-${id}"
|
|
placeholder="https://www.instagram.com/p/…">
|
|
</div>`,
|
|
footer: `
|
|
<button class="btn btn-secondary flex-1" id="qp-cancel">Abbrechen</button>
|
|
<button class="btn btn-primary flex-1" id="qp-ok-${id}"><svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#check"></use></svg> Bestätigen</button>`,
|
|
});
|
|
document.getElementById('qp-cancel')?.addEventListener('click', UI.modal.close);
|
|
document.getElementById(`qp-ok-${id}`)?.addEventListener('click', async () => {
|
|
const date = document.getElementById(`qp-date-${id}`)?.value
|
|
|| new Date().toISOString().slice(0,10);
|
|
const url = document.getElementById(`qp-url-${id}`)?.value || undefined;
|
|
await API.patch(`/social/content/${id}`, {
|
|
status: 'published',
|
|
published_at: date,
|
|
post_url: url,
|
|
});
|
|
UI.modal.close();
|
|
load(filter);
|
|
});
|
|
}));
|
|
|
|
// "Link eintragen" für bereits veröffentlichte Posts ohne URL
|
|
el.querySelectorAll('.sm-add-url').forEach(b => b.addEventListener('click', () => {
|
|
const id = b.dataset.id;
|
|
UI.modal.open({
|
|
title: '<svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#link-simple"></use></svg> Post-Link eintragen',
|
|
body: `
|
|
<div class="form-group">
|
|
<label class="form-label">Link zum veröffentlichten Post</label>
|
|
<input class="form-control" type="url" id="au-url-${id}"
|
|
placeholder="https://www.instagram.com/p/…" autofocus>
|
|
</div>`,
|
|
footer: `
|
|
<button class="btn btn-secondary flex-1" id="au-cancel">Abbrechen</button>
|
|
<button class="btn btn-primary flex-1" id="au-ok-${id}">💾 Speichern</button>`,
|
|
});
|
|
document.getElementById('au-cancel')?.addEventListener('click', UI.modal.close);
|
|
document.getElementById(`au-ok-${id}`)?.addEventListener('click', async () => {
|
|
const url = document.getElementById(`au-url-${id}`)?.value;
|
|
if (!url) return;
|
|
await API.patch(`/social/content/${id}`, { post_url: url });
|
|
UI.modal.close();
|
|
load(filter);
|
|
});
|
|
}));
|
|
el.querySelectorAll('.sm-exp').forEach(b => b.addEventListener('click', () => {
|
|
const d = el.querySelector(`#sm-d-${b.dataset.id}`);
|
|
if (d) { d.style.display = d.style.display==='none'?'':'none'; }
|
|
}));
|
|
el.querySelectorAll('.sm-copy').forEach(b => b.addEventListener('click', () => {
|
|
const text = b.getAttribute('data-copy')
|
|
.replace(/&/g,'&').replace(/</g,'<')
|
|
.replace(/>/g,'>').replace(/"/g,'"');
|
|
navigator.clipboard?.writeText(text).then(() => {
|
|
b.textContent = '<svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#check"></use></svg> Kopiert!';
|
|
setTimeout(() => b.textContent = '📋 Kopieren', 2000);
|
|
});
|
|
}));
|
|
el.querySelectorAll('.sm-sts').forEach(b => b.addEventListener('click', async () => {
|
|
await API.patch(`/social/content/${b.dataset.id}`, {status: b.dataset.s});
|
|
load(filter);
|
|
}));
|
|
el.querySelectorAll('.sm-del').forEach(b => b.addEventListener('click', async () => {
|
|
if (!window.confirm('Löschen?')) return;
|
|
await API.delete(`/social/content/${b.dataset.id}`);
|
|
load(filter);
|
|
}));
|
|
el.querySelectorAll('.sm-prev-arch').forEach(b => b.addEventListener('click', async () => {
|
|
const it = items.find(c => c.id === parseInt(b.dataset.id));
|
|
if (it) _showPreview(it);
|
|
}));
|
|
}
|
|
await load('alle');
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// BEWERTEN
|
|
// ---------------------------------------------------------------
|
|
function _renderBewerten(el) {
|
|
let selPlatform = 'instagram';
|
|
el.innerHTML = `
|
|
<div style="background:var(--c-surface);border-radius:var(--radius-lg);
|
|
box-shadow:var(--shadow-sm);padding:var(--space-4);
|
|
margin-bottom:var(--space-4);
|
|
display:flex;gap:var(--space-3)">
|
|
<span style="font-size:1.4em;flex-shrink:0">🌙</span>
|
|
<div style="font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.6">
|
|
Zeig mir deinen Entwurf — ich sage dir was gut ist und wie du ihn
|
|
noch besser machen kannst!</div>
|
|
</div>
|
|
<div style="background:var(--c-surface);border-radius:var(--radius-lg);
|
|
box-shadow:var(--shadow-sm);padding:var(--space-4)">
|
|
<div style="display:flex;gap:var(--space-2);margin-bottom:var(--space-4)">
|
|
${['instagram','tiktok','both'].map((p,i) => `
|
|
<button class="btn btn-sm sm-ep ${i===0?'btn-primary':'btn-secondary'}"
|
|
data-p="${p}" style="flex:1;min-height:36px;font-size:12px;padding:4px 8px;
|
|
border-radius:var(--radius-full)">
|
|
${_PL[p]}</button>`).join('')}
|
|
</div>
|
|
<textarea id="sm-draft" class="input"
|
|
style="width:100%;min-height:120px;resize:vertical;
|
|
font-size:var(--text-sm);line-height:1.5"
|
|
placeholder="Schreib hier deinen Caption-Entwurf oder einfach worum es im Post geht…"></textarea>
|
|
<button id="sm-eval" class="btn btn-primary"
|
|
style="margin-top:var(--space-4);width:100%;min-height:52px;
|
|
font-size:var(--text-base);border-radius:var(--radius-lg);
|
|
box-shadow:var(--shadow-md)">
|
|
🔍 Luna, schau mal drüber!
|
|
</button>
|
|
<div id="sm-eval-res" class="mt-4"></div>
|
|
</div>`;
|
|
|
|
el.querySelectorAll('.sm-ep').forEach(b => b.addEventListener('click', () => {
|
|
selPlatform = b.dataset.p;
|
|
el.querySelectorAll('.sm-ep').forEach(x =>
|
|
x.className = `btn btn-sm sm-ep ${x===b?'btn-primary':'btn-secondary'}`);
|
|
}));
|
|
|
|
el.querySelector('#sm-eval').addEventListener('click', async () => {
|
|
const draft = el.querySelector('#sm-draft').value.trim();
|
|
if (!draft) { UI.toast('Gib einen Text ein 😊', 'warning'); return; }
|
|
const btn = el.querySelector('#sm-eval');
|
|
const res = el.querySelector('#sm-eval-res');
|
|
btn.disabled = true;
|
|
res.innerHTML = _lunaProgressHtml();
|
|
const interval = _startProgress(res);
|
|
try {
|
|
const data = await API.post('/social/evaluate', {
|
|
platform: selPlatform, format: 'post', draft,
|
|
});
|
|
clearInterval(interval.bar); clearInterval(interval.msg);
|
|
_progressDone(res);
|
|
await new Promise(r => setTimeout(r, 400));
|
|
res.innerHTML = `
|
|
${data.notes ? `<div style="background:var(--c-primary-subtle);
|
|
border-radius:var(--radius-lg);padding:var(--space-4);margin-bottom:var(--space-3);
|
|
border-left:4px solid var(--c-primary);box-shadow:var(--shadow-xs)">
|
|
<div class="flex-gap-3">
|
|
<span style="font-size:1.3em;flex-shrink:0">🌙</span>
|
|
<div>
|
|
<div style="font-size:11px;font-weight:700;color:var(--c-primary);margin-bottom:4px;
|
|
text-transform:uppercase;letter-spacing:.5px">
|
|
Lunas Feedback:</div>
|
|
<div style="font-size:var(--text-sm);line-height:1.6">${_esc(data.notes)}</div>
|
|
</div>
|
|
</div>
|
|
</div>` : ''}
|
|
${_renderResult(data, null)}`;
|
|
_bindResultEvents(res);
|
|
API.get('/social/stats').then(s => { _stats = s; });
|
|
} catch(e) {
|
|
clearInterval(interval);
|
|
res.innerHTML = `<div class="text-danger">😬 Fehler: ${_esc(e.message||String(e))}</div>`;
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.innerHTML = '🔍 Luna, schau mal drüber!';
|
|
}
|
|
});
|
|
}
|
|
|
|
function _lunaBreedSuggestions(breeds, n = 3) {
|
|
// 3 zufällige Rassen aus den ersten 100 (bekannte Rassen)
|
|
const pool = breeds.slice(0, 100);
|
|
const picked = [];
|
|
const used = new Set();
|
|
while (picked.length < n && picked.length < pool.length) {
|
|
const i = Math.floor(Math.random() * pool.length);
|
|
if (!used.has(i)) { used.add(i); picked.push(pool[i]); }
|
|
}
|
|
return picked;
|
|
}
|
|
|
|
function _esc(s) {
|
|
if (!s) return '';
|
|
return String(s).replace(/&/g,'&').replace(/</g,'<')
|
|
.replace(/>/g,'>').replace(/"/g,'"');
|
|
}
|
|
|
|
// CSS
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
.sm-label{font-size:11px;font-weight:700;color:var(--c-text-muted);
|
|
text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px;display:block}
|
|
@keyframes luna-pulse{0%,100%{transform:scale(1)}50%{transform:scale(1.15)}}
|
|
#sm-breed-day:hover,#sm-training-tip:hover,#sm-pflege-tip:hover{
|
|
transform:translateY(-2px);box-shadow:var(--shadow-md)!important}
|
|
#sm-breed-day:active,#sm-training-tip:active,#sm-pflege-tip:active{
|
|
transform:translateY(0)}
|
|
#sm-show-exercises:hover{background:var(--c-surface-2)!important;
|
|
border-color:var(--c-border)!important;color:var(--c-text-secondary)!important}
|
|
.sm-breed-chip:hover{background:var(--c-primary-subtle)!important;
|
|
border-color:var(--c-primary)!important;color:var(--c-primary-dark)!important}
|
|
`;
|
|
document.head.appendChild(style);
|
|
|
|
return { init, refresh };
|
|
})();
|