Das Rudel meint den ganzen Raum -- und die Kachelfarbe wird ein Ring

Zwei Auftraege vom 23.09.2026.

SCREEN 1: "da steht links immer noch der treff anstatt das rudel"

Und er hatte recht, auf eine Art, die niemand sehen konnte:
TREFF_NAME stand schon seit der Umbenennung auf "Das Rudel". Nur
setzt treffRaumId() den Namen ausschliesslich BEIM ANLEGEN -- der
Raum existierte laengst, also blieb die Zeile in der Datenbank auf
"Der Treff" stehen. Im Code das eine, auf dem Bildschirm das andere.

Dieselbe Falle stand direkt nebenan schon beschrieben ("drei Stellen,
von denen die dritte vergessen wird") -- die Vorsorge galt aber nur
fuer die Teilnehmer, nicht fuer den Namen. Eine halbe Selbstheilung
heilt die andere Haelfte nicht. Jetzt gleicht treffAngleichen() auch
den Namen ab; eine kuenftige Umbenennung ist wieder eine Zeile.
(IS NOT statt !=: Bei NULL ergaebe != in SQLite NULL, und die Zeile
bliebe unveraendert liegen.)

"und wenn wir da im chat @rudel machen will ich dass jeder in dem
chat markiert wird. nicht nur team ... und sehr wichtig ich rede nur
von dem chat wo die ganze community auch drin ist."

Bis heute erreichte der Ruf ueberall nur Team Dogi. Die Begruendung
stand im Code und war nicht falsch -- im Rudel-Raum sitzt die
Community. Genau das will Filipe jetzt, und zwar nur dort:

  im Rudel-Raum   -> alle, die drin sind
  ueberall sonst  -> Team Dogi, unveraendert

Was die alte Sorge entschaerft: RUFEN darf weiterhin nur Team Dogi --
kein Zuschauer kann alle wecken. Und die Nachtruhe gilt dort ohnehin.

darf_rudel folgt derselben Frage. Sonst entstuende ein stiller
Widerspruch: Ein Modi mit neun Zuschauern und ohne zweites
Teammitglied traefe mit @rudel neun Leute -- der Vorschlag beim
Tippen waere aber ausgeblendet. Die Funktion gaebe es, und niemand
faende sie.

Das Schild sagt jetzt die Wahrheit: "alle in diesem Chat" statt
"alle im Team". Stuende dort weiter das alte, waere es im Rudel-Raum
gelogen -- und zwar nach unten, also in die Richtung, in der man
leichtfertig drueckt.

Gegenprobe in pruef-erwaehnung: In der Testgruppe sitzen DogFather
und zwei Scouts; ein @rudel dort ruft NIEMANDEN. Waere die Regel
versehentlich im ganzen Haus aktiv, waeren die Scouts markiert.
"sonst nichts anfassen" ist damit gemessen, nicht versprochen.

SCREEN 2: "so eine art diagramm, wo man mit einem kreis herum gehen
kann und die farbe ganz genau selber auswaehlen kann"

Der Haken daran ist nicht die Optik. Die zwoelf Kacheln waren
GERECHNET, damit niemand sich unlesbar machen kann -- alle auf
derselben Leuchtdichte. Ein Farbkreis, der einfach den Farbton dreht,
wirft das weg: Ein gesaettigtes Gelb ist um ein Vielfaches heller als
ein gesaettigtes Blau, und helle Schrift ist darauf nicht mehr zu
lesen.

Der Ring ist deshalb KEINE neue Rechnung, sondern dieselbe in 360
Schritten statt in zwoelf. tools/chat-kacheln-rechnen.mjs --kreis
schreibt server/chat-kreis.js; buntest() und die Kontrastpruefung
darin gelten unveraendert. Ergebnis: ein Ring gleicher Helligkeit --
kein blendendes Gelb, kein abgesoffenes Blau. Das sieht ruhiger aus
als der uebliche Farbkreis, und genau das ist der Punkt.

Der Browser rechnet nichts. Er bekommt die 360 fertigen Farben und
legt sie in dieselbe Tabelle wie die Kacheln -- ab da ist "ton-214"
ein Schluessel wie "veilchen", und jede Stelle, die eine Farbe
nachschlaegt, funktioniert unveraendert.

Welche Kacheln neben dem Ring bleiben, wird ABGELEITET statt
aufgezaehlt: Grau hat keinen Farbton, Babyblau ist die eine helle mit
eigener Schrift. Nachgemessen liegen die elf bunten zwischen 0,0 und
2,4 von ihrer Ringfarbe entfernt, Grau bei 75,4 und Babyblau bei
192,9 -- zwischen 2,4 und 75 ist so viel Luft, dass die Schwelle
nicht knapp ist.

Gespeichert wird erst beim Loslassen. Wer einmal um den Ring fuehrt,
erzeugte sonst dreihundert Anfragen.

Gemessen (pruef-chat-neu, jetzt 32 Pruefungen, als Modi auf crew. mit
dem Finger): 265 px gross, aus 361 Farben gemalt, touch-action none
(sonst schiebt das Handy die Seite weg statt zu drehen), Drehen auf
drei Uhr ergibt genau Winkel 90, die Mitte zeigt exakt #675102,
vorgelesen als "Goldbraun, 90 Grad", in der Datenbank steht ton-90,
Pfeiltaste dreht ein Grad weiter. Und das eigentliche Versprechen:
alle 360 Toene tragen die Schrift der Blase, knappster Faktor 1,000.

Dabei zwei eigene Fehler, beide aufgeschrieben: Der erste Anlauf
wartete 30 Sekunden auf den Farbknopf -- am Handy weicht die Spalte
mit den Gespraechen zur Seite, sobald ein Chat offen ist. "Ist im
HTML" und "ist erreichbar" sind zwei Aussagen. Und die Schriftfarben
wurden an der falschen Klasse gemessen, was einen roten Haken an
einer heilen Stelle ergab.

Ausserdem: Das native confirm() beim Herausnehmen eines GIFs ist
weg -- meine eigene Abkuerzung vom Morgen. Gemeldet hat es
pruef-nachfrage, allerdings erst, nachdem der verlorene Backslash in
derselben Datei repariert war.

Zahlen: pruef-erwaehnung 122 (vorher 119), pruef-chat-neu 32
(vorher 19), pruef-nachfrage 46 bestanden.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-23 14:30:02 +02:00
co-authored by Claude Opus 5
parent a9d920e7d0
commit 7a6749630c
9 changed files with 986 additions and 41 deletions
+116 -2
View File
@@ -2483,7 +2483,7 @@
Tönungen gar nicht sieht. Es steht in derselben Farbe wie die
Schrift -- eine zweite Farbe wäre dieselbe Wette wie oben. */
.chat-erwaehnung[data-rudel="ja"]::before {
content: "C9A0";
content: "\25C9\A0";
opacity: .72;
}
@@ -2721,7 +2721,7 @@
box-shadow: 0 0 0 2px color-mix(in srgb, var(--akzent) 45%, transparent);
}
.chat-kachelwahl__ton[aria-checked="true"]::after {
content: "¹3";
content: "\2713";
position: absolute; top: 3px; right: 6px;
font-size: .8rem; font-weight: 700;
color: var(--akzent);
@@ -3033,3 +3033,117 @@
.gif-kachel__knopf { border: 1px solid CanvasText; }
.chat__gif[aria-expanded="true"] { border: 2px solid Highlight; }
}
/* =====================================================================
DER FARBRING IN DER KACHELWAHL (23.09.2026)
Filipe: „kannst du nicht so eine art diagramm machen wo man mit
einem kreis herum gehen kann und sie die farbe ganz genau selber
auswählen kann? ... es soll richtig krass geil aussehen."
Der Ring wird in chat.js mit 360 fertig gerechneten Farben gefüllt.
Hier steht nur, wie er aussieht und wie man ihn anfasst.
WARUM ER RUHIG WIRKT UND NICHT GRELL: Alle 360 Töne liegen auf
derselben Leuchtdichte. Ein üblicher Farbkreis hat ein blendendes
Gelb und ein fast schwarzes Blau; dieser hier nicht. Das ist keine
Zurückhaltung im Geschmack, sondern die Bedingung dafür, dass die
Schrift auf JEDER dieser Farben lesbar bleibt — und nebenbei sieht
ein Ring gleicher Helligkeit deutlich edler aus als der bunte
Standardkreis, den man von überall kennt.
===================================================================== */
.kachelrad {
position: relative;
width: min(17rem, 68vw);
aspect-ratio: 1;
margin: 18px auto 6px;
/* Der Griff liegt auf dem Ring und darf nicht abgeschnitten werden. */
overflow: visible;
}
.kachelrad[hidden] { display: none; }
.kachelrad__ring {
position: absolute;
inset: 0;
border-radius: 50%;
/* Der Farbverlauf selbst kommt aus chat.js -- 360 Werte. */
/* DIE MITTE WIRD AUSGESTANZT statt mit einer Scheibe zugedeckt:
Eine Scheibe darüber würde die Zeigerbewegung abfangen, und der
Ring reagierte in der Mitte nicht mehr. */
-webkit-mask: radial-gradient(circle, #0000 0 56%, #000 57% 100%);
mask: radial-gradient(circle, #0000 0 56%, #000 57% 100%);
/* OHNE DAS SCROLLT DAS HANDY. Wer auf einem Berührungsbildschirm
im Kreis wischt, schiebt sonst die Seite weg, und der Ring
bekommt die Bewegung nie zu sehen. */
touch-action: none;
cursor: grab;
/* Eine leise Tiefe, damit der Ring auf dem dunklen Grund steht und
nicht schwebt. Kein Leuchten, kein Puls -- augenschonend gilt
hier genauso. */
box-shadow:
inset 0 0 0 1px color-mix(in srgb, #000 55%, transparent),
0 6px 20px -8px #000;
}
.kachelrad__ring:active { cursor: grabbing; }
.kachelrad__ring:focus-visible {
outline: 3px solid var(--akzent);
outline-offset: 4px;
}
/* Der Arm dreht sich, der Griff sitzt an seinem oberen Ende.
So braucht der Griff keine eigene Winkelrechnung -- er wird
einfach mitgedreht. */
.kachelrad__arm {
position: absolute;
inset: 0;
transform: rotate(var(--winkel, 0deg));
pointer-events: none;
}
.kachelrad__griff {
position: absolute;
top: 8.5%;
left: 50%;
width: 2.3rem;
height: 2.3rem;
margin: -1.15rem 0 0 -1.15rem;
border-radius: 50%;
border: 3px solid #fff;
background: transparent;
box-shadow:
0 0 0 2px color-mix(in srgb, #000 70%, transparent),
0 2px 8px -2px #000;
}
/* Die Mitte zeigt, wie die eigene Nachricht aussehen wird -- mit
echtem Text in der echten Schriftfarbe dieser Kachel. Ein reiner
Farbfleck beantwortet die Frage nicht, die man hier hat. */
.kachelrad__mitte {
position: absolute;
inset: 22%;
border-radius: 999px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
font-weight: 600;
pointer-events: none;
box-shadow: 0 0 0 1px color-mix(in srgb, #000 45%, transparent);
}
.kachelrad__probe { opacity: .95; }
/* Ein Wechsel der Farbe soll nicht springen, aber auch nicht
nachlaufen -- 90 ms sind gerade so viel, dass es weich wirkt. */
@media (prefers-reduced-motion: no-preference) {
.kachelrad__arm { transition: transform .09s linear; }
.kachelrad__mitte { transition: background-color .09s linear; }
}
/* Im Kontrastmodus gibt es keine Farbflächen. Dann ist ein Farbring
sinnlos -- die Sonderknöpfe darunter tragen weiter, und der Ring
bekommt wenigstens einen sichtbaren Umriss statt zu verschwinden. */
@media (forced-colors: active) {
.kachelrad__ring { border: 1px solid CanvasText; }
.kachelrad__griff { border-color: Highlight; }
.kachelrad__mitte { border: 1px solid CanvasText; }
}
+262 -7
View File
@@ -54,6 +54,9 @@
Fassung, die beim naechsten Ton aelter ist als die andere. */
const KACHEL_SCHRIFT = Object.create(null);
let kachelListe = [];
/* Die 360 Farben des Rings, Index = Winkel. Kommt vom Server; hier
wird nichts gerechnet (Begruendung steht an der Route). */
let kreisToene = [];
let meineKachel = null;
let autoKachel = null;
@@ -73,6 +76,20 @@
KACHEL_FARBE[k.wert] = k.farbe;
if (k.schrift) KACHEL_SCHRIFT[k.wert] = k.schrift;
}
/* DER KREIS KOMMT IN DIESELBE TABELLE.
Das ist der ganze Trick an dieser Stelle: Ab hier ist
"ton-214" ein Schluessel wie "veilchen" auch. blaseFaerben(),
der Knopf in der Kopfzeile und jede andere Stelle, die eine
Farbe nachschlaegt, funktionieren unveraendert weiter -- ohne
dass irgendwo eine zweite Art Wahl behandelt werden muesste.
Eine Sonderbehandlung an zehn Stellen waere neun Gelegenheiten
gewesen, eine davon zu vergessen. */
kreisToene = Array.isArray(d.kreis) ? d.kreis : [];
kreisToene.forEach((farbe, winkel) => {
KACHEL_FARBE['ton-' + winkel] = farbe;
if (d.kreis_schrift) KACHEL_SCHRIFT['ton-' + winkel] = d.kreis_schrift;
});
kachelKnopfZeigen();
} catch { /* ohne die Liste bleibt alles beim Standard -- kein Grund
zu scheitern, der Chat funktioniert weiter */ }
@@ -96,11 +113,56 @@
}
}
/* WELCHE KACHELN NEBEN DEM RING BLEIBEN -- abgeleitet, nicht
aufgezaehlt.
Auf dem Ring liegt alles, was einen Farbton hat und auf der
gemeinsamen Leuchtdichte sitzt. Zwei Kacheln koennen das nicht:
Grau hat keinen Farbton, und Babyblau ist die eine helle mit
eigener, dunkler Schrift. Beide gehoeren weiter dazu.
Eine Liste ["grund","babyblau"] waere die naheliegende Loesung und
genau die Falle vom 11.09.: Kommt morgen eine dritte Sonderkachel
dazu, fehlt sie stillschweigend im Waehler. Stattdessen wird
gemessen -- liegt die Farbe nahe an einer Ringfarbe, ist sie auf
dem Ring; sonst braucht sie einen eigenen Knopf.
DIE SCHWELLE IST NICHT GERATEN, sondern nachgemessen (23.09.2026):
Die elf bunten Kacheln liegen zwischen 0,0 und 2,4 von ihrer
Ringfarbe entfernt, Grau bei 75,4 und Babyblau bei 192,9. Zwischen
2,4 und 75 ist so viel Luft, dass jede Zahl darin dieselbe Antwort
gibt -- die Schwelle ist also keine knappe Entscheidung, die beim
naechsten Nachrechnen kippt. */
const RINGNAH = 12;
function abstand(a, b) {
if (!a || !b || a.length !== 7 || b.length !== 7) return Infinity;
let s = 0;
for (const i of [1, 3, 5]) {
const d2 = parseInt(a.slice(i, i + 2), 16) - parseInt(b.slice(i, i + 2), 16);
s += d2 * d2;
}
return Math.sqrt(s);
}
/** Der Winkel, dessen Farbe dieser am naechsten kommt -- samt Abstand. */
function naechsterWinkel(farbe) {
let best = Infinity, winkel = 0;
kreisToene.forEach((t, i) => {
const d2 = abstand(t, farbe);
if (d2 < best) { best = d2; winkel = i; }
});
return { winkel, abstand: best };
}
function kachelwahlBauen() {
radBauen();
const gitter = $('kachel-gitter');
if (!gitter) return;
gitter.textContent = '';
for (const k of kachelListe) {
/* Liegt sie auf dem Ring, hat sie dort schon ihren Platz. */
if (kreisToene.length && naechsterWinkel(k.farbe).abstand <= RINGNAH) continue;
const b = el('button', 'chat-kachelwahl__ton');
b.type = 'button';
b.dataset.wert = k.wert;
@@ -133,9 +195,12 @@
halbe Sekunde nichts sieht, tippt noch einmal. Geht es schief,
wird es zurueckgenommen -- und zwar sichtbar. */
meineKachel = (wert === 'grund') ? null : wert;
for (const b of knopf.parentElement.querySelectorAll('.chat-kachelwahl__ton')) {
b.setAttribute('aria-checked', String(b === knopf));
}
/* OHNE DEN GEDRUECKTEN KNOPF ZU KENNEN. Vorher wurde ueber
knopf.parentElement markiert -- seit der Ring dazugekommen ist,
gibt es Aufrufe ganz ohne Knopf, und die waeren daran
abgestuerzt. Der Zustand steht in meineKachel; die Anzeige wird
daraus abgeleitet. */
auswahlMarkieren();
kachelKnopfZeigen();
eigeneKachelnFaerben();
try {
@@ -185,7 +250,161 @@
}
}
/** Markiert im Waehler, was gerade gewaehlt ist.
*
* NUR DIE KNOEPFE, NICHT DER RING. Der Ring wird von radZeigen()
* gestellt -- riefe er hier zurueck, riefen sich die beiden
* gegenseitig auf. Es endete zwar nach zwei Schritten, aber wer
* spaeter eine Zeile dazwischensetzt, baut daraus eine Schleife,
* die den Browser anhaelt. Also gar nicht erst. */
function auswahlMarkieren() {
const gitter = $('kachel-gitter');
if (!gitter) return;
for (const b of gitter.querySelectorAll('.chat-kachelwahl__ton')) {
b.setAttribute('aria-checked', String((meineKachel || 'grund') === b.dataset.wert));
}
}
/* =====================================================================
DER FARBRING (23.09.2026)
Filipe: „kannst du nicht so eine art diagramm machen wo man mit
einem kreis herum gehen kann und sie die farbe ganz genau selber
auswaehlen kann?"
---------------------------------------------------------------------
GESPEICHERT WIRD ERST, WENN MAN LOSLAESST
Wer einmal um den Ring fuehrt, erzeugt sonst dreihundert
Speicheranfragen -- auf einem Handy im Zug heisst das: der Ring
haengt, und die Farbe, die am Ende ankommt, ist die aus der Mitte
der Bewegung. Die Vorschau laeuft deshalb sofort und oertlich, die
Anfrage geht einmal, wenn die Bewegung endet. Bei der Tastatur
dasselbe mit einer halben Sekunde Ruhe.
DER RING IST NICHT DIE WAHRHEIT, DIE TABELLE IST ES. Der
Farbverlauf blendet zwischen benachbarten Winkeln weich ueber;
was gewaehlt wird, kommt immer aus kreisToene[winkel]. Sonst
haette man eine Farbe, die auf dem Bildschirm ausgerechnet
zwischen zwei geprueften Werten liegt.
===================================================================== */
let radWinkel = 0;
let radUhr = null;
function radBauen() {
const ring = $('kachelrad-ring');
const rad = $('kachelrad');
if (!ring || !rad) return;
/* OHNE KREIS KEIN RING. Kommt die Liste nicht (alter Server,
Netzproblem), bleibt das Rad weg und die Sonderknoepfe
darunter tun weiter ihren Dienst -- statt eines leeren,
schwarzen Kreises, bei dem jeder den Fehler bei sich sucht. */
if (kreisToene.length < 2) { rad.hidden = true; return; }
rad.hidden = false;
ring.style.background = 'conic-gradient(' + kreisToene.join(',') + ',' + kreisToene[0] + ')';
/* Wo der Griff steht, wenn man aufmacht: bei der eigenen Farbe.
Wer Babyblau traegt, hat keinen Winkel -- dann zeigt der Griff
auf den naechstgelegenen, ohne dass etwas umgestellt wird. */
const jetzt = KACHEL_FARBE[meineKachel] || KACHEL_FARBE[autoKachel] || null;
const t = /^ton-(\d{1,3})$/.exec(String(meineKachel || ''));
radWinkel = t ? Number(t[1]) : (jetzt ? naechsterWinkel(jetzt).winkel : 0);
radZeigen(radWinkel, { nurAnzeige: true });
}
/** Griff, Vorschau und Vorlesewert auf diesen Winkel stellen. */
function radZeigen(winkel, { nurAnzeige = false } = {}) {
const ring = $('kachelrad-ring'), arm = $('kachelrad-arm'), mitte = $('kachelrad-mitte');
if (!ring || !arm) return;
const w = ((Math.round(winkel) % 360) + 360) % 360;
radWinkel = w;
arm.style.setProperty('--winkel', w + 'deg');
ring.setAttribute('aria-valuenow', String(w));
/* EINE ZAHL IN GRAD SAGT NIEMANDEM ETWAS. Vorgelesen wird der
Farbname, den ein Mensch benutzt -- "Blaugruen, 190 Grad". */
ring.setAttribute('aria-valuetext', farbwort(w) + ', ' + w + ' Grad');
if (mitte) {
const farbe = kreisToene[w];
mitte.style.background = farbe || '';
const s = KACHEL_SCHRIFT['ton-' + w];
mitte.style.color = (s && s.text) || '';
}
if (!nurAnzeige) {
/* Die eigenen Blasen im Verlauf faerben sich mit -- man
entscheidet sich ja nicht fuer einen Fleck, sondern dafuer,
wie die eigenen Nachrichten aussehen. */
meineKachel = 'ton-' + w;
kachelKnopfZeigen();
eigeneKachelnFaerben();
auswahlMarkieren();
}
}
/** Ein Wort fuer den Bereich, damit eine Sprachausgabe etwas sagen kann. */
function farbwort(w) {
const namen = [
[0, 'Rot'], [20, 'Orangerot'], [45, 'Orange'], [70, 'Goldbraun'],
[95, 'Oliv'], [120, 'Gruen'], [155, 'Tannengruen'], [180, 'Blaugruen'],
[205, 'Tuerkis'], [230, 'Blau'], [265, 'Indigo'], [290, 'Violett'],
[315, 'Magenta'], [340, 'Pink'],
];
let wort = namen[0][1];
for (const [ab, n] of namen) if (w >= ab) wort = n;
return wort;
}
/** Nach der Bewegung einmal speichern. */
function radSichern(verzoegerung = 0) {
clearTimeout(radUhr);
radUhr = setTimeout(() => kachelSetzen('ton-' + radWinkel), verzoegerung);
}
function radBedienen() {
const ring = $('kachelrad-ring');
if (!ring) return;
/* Aus einem Punkt auf dem Bildschirm wird ein Winkel.
atan2(dx, -dy) zaehlt bei ZWOELF UHR los und laeuft im
Uhrzeigersinn -- genau so, wie conic-gradient den Ring malt.
Damit stimmt der Griff mit der Farbe darunter ueberein; mit dem
ueblichen atan2(dy, dx) laege er um 90 Grad daneben. */
const winkelAus = (ev) => {
const r = ring.getBoundingClientRect();
const dx = ev.clientX - (r.left + r.width / 2);
const dy = ev.clientY - (r.top + r.height / 2);
return (Math.atan2(dx, -dy) * 180 / Math.PI + 360) % 360;
};
let zieht = false;
ring.addEventListener('pointerdown', (ev) => {
zieht = true;
ring.setPointerCapture(ev.pointerId);
radZeigen(winkelAus(ev));
ev.preventDefault();
});
ring.addEventListener('pointermove', (ev) => { if (zieht) radZeigen(winkelAus(ev)); });
const los = () => { if (zieht) { zieht = false; radSichern(0); } };
ring.addEventListener('pointerup', los);
ring.addEventListener('pointercancel', los);
/* MIT DER TASTATUR GEHT ES AUCH. Ein Ring, den man nur mit der
Maus bedienen kann, ist fuer jeden ohne Maus eine Sackgasse --
und der Chat ist die Seite, auf der am meisten passiert. */
ring.addEventListener('keydown', (ev) => {
const schritt = { ArrowRight: 1, ArrowUp: 1, ArrowLeft: -1, ArrowDown: -1,
PageUp: 15, PageDown: -15 }[ev.key];
if (schritt === undefined) {
if (ev.key === 'Home') { radZeigen(0); radSichern(500); ev.preventDefault(); }
return;
}
radZeigen(radWinkel + schritt * (ev.shiftKey ? 5 : 1));
radSichern(500);
ev.preventDefault();
});
}
function kachelwahlBedienen() {
radBedienen();
const knopf = $('kachel-oeffnen'), dlg = $('kachel-wahl'), zu = $('kachel-zu');
if (!knopf || !dlg) return;
knopf.addEventListener('click', () => { kachelwahlBauen(); dlg.showModal(); });
@@ -226,6 +445,12 @@
Server hinterher erkennt. */
let darfRudel = false;
let rudelWort = 'rudel';
/* WEN "@rudel" IN DIESEM RAUM MEINT -- vom Server, nicht geraten.
'raum' = alle Anwesenden (so im Rudel-Raum), 'team' = Team Dogi
(ueberall sonst). Steht die Unterscheidung auch hier, gibt es
zwei Wahrheiten, und die Liste verspricht irgendwann etwas
anderes, als der Ruf hinterher tut. */
let rudelWen = 'team';
let darfKanal = false;
/* Was der Server ueber die Nachtruhe sagt. `null` heisst: Diese
Person hat mit dem Treff nichts zu tun -- dann gibt es auch
@@ -780,6 +1005,7 @@
darfAllesLoeschen = !!daten.darf_alles_loeschen;
darfRudel = !!daten.darf_rudel;
rudelWort = daten.rudel_wort || 'rudel';
rudelWen = daten.rudel_wen === 'raum' ? 'raum' : 'team';
/* DER MIKROFONKNOPF ERSCHEINT, WENN DER SERVER ES SAGT
(23.09.2026). Filipe: „also nur die modis rechte linke hand
und dogfather." Hier wird keine Rolle verglichen -- dieselbe
@@ -1881,9 +2107,18 @@
if (t.rudel) { b.dataset.rudel = 'ja'; z.dataset.rudel = 'ja'; }
b.append(z, el('span', 'erwaehn-liste__name', t.name));
/* WAS ES BEDEUTET, STEHT DANEBEN. "rudel" allein sagt keinem
Neuen, dass damit das ganze Team gemeint ist -- und wer das
erst nach dem Absenden erfaehrt, hat schon alle geweckt. */
if (t.rudel) b.append(el('span', 'erwaehn-liste__rolle', 'alle im Team'));
Neuen, wen er damit weckt -- und wer das erst nach dem
Absenden erfaehrt, hat schon alle geweckt.
Und es sagt das RICHTIGE: Im Rudel-Raum trifft der Ruf jeden
Anwesenden, sonst nur Team Dogi. Stuende hier immer "alle im
Team", waere das im Rudel-Raum schlicht gelogen -- und zwar
nach unten, also in die Richtung, in der man leichtfertig
drueckt. */
if (t.rudel) {
b.append(el('span', 'erwaehn-liste__rolle',
rudelWen === 'raum' ? 'alle in diesem Chat' : 'alle im Team'));
}
else if (ROLLENKURZ[t.rolle]) b.append(el('span', 'erwaehn-liste__rolle', ROLLENKURZ[t.rolle]));
/* mousedown statt click: Ein Klick nimmt dem Textfeld zuerst den
Schreibzeiger, und danach weiss niemand mehr, wohin der Name
@@ -2923,7 +3158,27 @@
weg.setAttribute('aria-label', `GIF ${g.name} aus der Kiste nehmen`);
weg.addEventListener('click', async (e) => {
e.stopPropagation();
if (!confirm(`„${g.name}" aus der Kiste nehmen? Verschickte GIFs bleiben stehen.`)) return;
/* MIT DER RUECKFRAGE DES HAUSES, nicht mit confirm().
Am 23.09.2026 stand hier ein natives confirm() -- meine
eigene Abkuerzung beim Bau der Kiste am selben Morgen.
pruef-nachfrage bewacht genau das und hat es gemeldet,
allerdings erst, nachdem eine kaputte Stelle in derselben
Datei repariert war (ein verlorener Backslash hatte ihr
Suchmuster stillgelegt).
WARUM DAS HAUS KEINE NATIVEN DIALOGE WILL: Sie sehen auf
jedem Geraet anders aus, lassen sich nicht auf Deutsch
beschriften, haben keinen Platz fuer die Frage "was bleibt
danach?" -- und auf manchen Handys unterdrueckt man sie
einmal und bekommt sie nie wieder, dann loescht der Knopf
kommentarlos. */
if (!await frageNach({
titel: 'Aus der Kiste nehmen?',
was: `„${g.name}" ist danach fuer das ganze Team weg.`,
bleibt: 'Schon verschickte GIFs bleiben in den Gespraechen stehen.',
endgueltig: true, ja: 'Herausnehmen',
})) return;
try {
const a = await hole(`/workspace/api/chat/gifs/${g.id}`, { method: 'DELETE' });
const j = await a.json().catch(() => ({}));