Show NeuralNet unread messages on dashboard
mindflux-ci / build (push) Successful in 23s

This commit is contained in:
AliceDarkest
2026-07-14 16:54:29 +02:00
parent 42d0342981
commit ac5e6e6496
3 changed files with 33 additions and 1 deletions
+3
View File
@@ -7,6 +7,9 @@ Versionierung [semantisch](https://semver.org/lang/de/).
## [Unreleased] – 2026-07-14 · SysnaptiK Neural Rebrand
### Marke & Plattform
- **NeuralNet-Benachrichtigung:** Die Dashboard-Glocke ist jetzt eine
Sprechblase. Sie zeigt den echten Zähler ungelesener Direktnachrichten und
leuchtet in der Neural-Akzentfarbe, bis die Nachrichten gelesen sind.
- **NeuralNet-Chatlayout geschlossen:** Chatkopf, Nachrichtenliste und Eingabefeld
bilden nun eine feste Flex-Spalte im Panel. Der Footer kann nicht mehr zwischen
Gespräch und Eingabe rutschen.
+1
View File
@@ -492,6 +492,7 @@ Der Verdacht „Profilbutton fehlt / Einstellungen nicht im Profil / irgendwas i
> Der Gesprächsbereich öffnet unmittelbar beim Auswählen einer bestätigten Verbindung.
> Die dynamisch gerenderten Kontakt- und Chat-Aktionen werden direkt gebunden.
> Der Chatbereich hält Kopf, Verlauf und Eingabe als zusammenhängende Spalte.
> Die Dashboard-Sprechblase zeigt im 30-Sekunden-Takt echte ungelesene Nachrichten.
> Blockieren, Melden/Moderation, Duelle und Lernlobby bleiben die nächsten Ausbauschritte.
> Ersetzt den `/soziales`-Platzhalter („Straßen? Wo wir hingehen, brauchen wir keine Straßen!"). Ziel: User können sich befreunden, intern miteinander schreiben, gemeinsam lernen und Achievements vergleichen – **vollständig selbst gehostet, ohne einen einzigen externen Dienst**, und DSGVO-konform von Grund auf.
+29 -1
View File
@@ -25,6 +25,13 @@
.status-details b { color:var(--text); font-weight:600; text-align:right; }
.status-toggle { grid-column:1 / -1; justify-self:start; border:0; padding:0; background:transparent; color:var(--secondary); font:inherit; font-size:.72rem; cursor:pointer; }
.status-toggle:hover { text-decoration:underline; }
.nn-trigger { position:relative; color:var(--secondary); }
.nn-trigger.unread { color:var(--accent); border-color:color-mix(in srgb,var(--accent) 72%,var(--border)); background:color-mix(in srgb,var(--accent) 16%,var(--surface)); box-shadow:0 0 18px var(--glow-s); animation:nn-notification-pulse 1.8s ease-in-out infinite; }
.nn-trigger svg { width:20px; height:20px; }
.nn-unread { position:absolute; top:-6px; right:-7px; min-width:17px; height:17px; padding:0 4px; display:grid; place-items:center; border:2px solid var(--surface); border-radius:99px; background:var(--accent); color:var(--accent-ink); font-size:.62rem; font-weight:900; line-height:1; box-shadow:0 0 10px var(--glow-s); }
.nn-unread[hidden] { display:none; }
@keyframes nn-notification-pulse { 50% { box-shadow:0 0 25px var(--glow-s); transform:translateY(-1px); } }
@media (prefers-reduced-motion:reduce) { .nn-trigger.unread { animation:none; } }
.prog-top { display:flex; justify-content:space-between; align-items:center; margin-bottom:.4rem; }
.pill { font-size:.68rem; color:var(--muted); border:1px solid var(--border); border-radius:7px; padding:.2rem .5rem; }
.prog-big { display:flex; align-items:baseline; gap:.5rem; }
@@ -64,7 +71,7 @@
</svg>
<span class="ft-trigger-dot" aria-hidden="true"></span>
</button>
<a class="mf-iconbtn" href="/soziales" aria-label="NeuralNet öffnen" title="NeuralNet"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/></svg></a>
<a class="mf-iconbtn nn-trigger" href="/soziales" data-neuralnet-trigger aria-label="NeuralNet – keine ungelesenen Nachrichten" title="NeuralNet"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 11.5a7.5 7.5 0 0 1-10.9 6.7L4 20l1.8-4.7A7.5 7.5 0 1 1 20 11.5Z"/><path d="M8.5 11.5h.01M12 11.5h.01M15.5 11.5h.01"/></svg><span class="nn-unread" data-neuralnet-unread hidden aria-hidden="true"></span></a>
<div class="mf-search"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4-4"/></svg><input placeholder="Suche..."></div>
<button class="mf-theme"></button>
<form th:action="@{/logout}" method="post" style="display:inline">
@@ -172,6 +179,27 @@
const line = `M${points.join(' L')}`;
weekChart.querySelector('svg').innerHTML = `<defs><linearGradient id="ar" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="var(--accent)" stop-opacity=".35"/><stop offset="1" stop-color="var(--accent)" stop-opacity="0"/></linearGradient></defs><path d="${line} L240 74 L0 74 Z" fill="url(#ar)"/><path d="${line}" fill="none" stroke="var(--accent)" stroke-width="2" style="filter:drop-shadow(0 0 5px var(--glow-s))"/>`;
// NeuralNet: Die Sprechblase zeigt ausschließlich echte, ungelesene
// Direktnachrichten. Aktualisierung erfolgt schlank im Hintergrund.
const neuralNetTrigger = document.querySelector('[data-neuralnet-trigger]');
const neuralNetUnread = document.querySelector('[data-neuralnet-unread]');
function refreshNeuralNetNotice() {
fetch('/api/neuralnet/overview', { headers: { Accept: 'application/json' } })
.then(response => response.ok ? response.json() : null)
.then(overview => {
const unread = overview && overview.enabled ? overview.unread || 0 : 0;
neuralNetTrigger.classList.toggle('unread', unread > 0);
neuralNetUnread.hidden = unread === 0;
neuralNetUnread.textContent = unread > 99 ? '99+' : unread;
neuralNetTrigger.setAttribute('aria-label', unread > 0
? `NeuralNet – ${unread} ungelesene Nachricht${unread === 1 ? '' : 'en'}`
: 'NeuralNet – keine ungelesenen Nachrichten');
})
.catch(() => { neuralNetTrigger.classList.remove('unread'); neuralNetUnread.hidden = true; });
}
refreshNeuralNetNotice();
setInterval(refreshNeuralNetNotice, 30000);
// Der Status prüft den Fortschrittsspeicher serverseitig. Die Modulzahl ist
// der registrierte Lernkatalog, nicht ein erfundener Prozentwert.
const statusCard = document.getElementById('platformStatus');