Der Anrufkasten zeigt, WER dran ist -- nicht dreimal "Verbunden"

Filipe, zu Screen 1: "wenn ich anrufe soll das auch viel besser geiler
und spezieller aussehen aber es soll nicht raushaengen."

Erst gemessen: Es haengt nichts raus. Der Kasten ist 374 px am Handy,
340 am Rechner, 72 Teile geprueft, kein einziges ueber dem Rand. Die
Optik war also gar nicht der Mangel -- zwei andere Dinge waren es.

1. DER NAME DES ANRUFERS GING IMMER VERLOREN.
   `wer(raumName || "Anruf")` lief VOR `kastenBauen()`. Das Element
   #anruf-wer gab es zu dem Zeitpunkt noch nicht, die Zuweisung lief
   ins Leere, und oben stand bei jedem Anruf das Wort "Anruf". Die
   Reihenfolge ist umgedreht: erst bauen, dann beschriften.

2. IN EINEM ANRUF OHNE VIDEO STAND JE PERSON DAS WORT "Verbunden".
   Bei dreien dreimal dasselbe Wort -- und man sah nicht, WER dabei
   war. In einer Gruppe ist genau das die Frage: Ist Kessi schon
   drin? Jetzt steht dort ein Kreis mit den Anfangsbuchstaben und der
   echte Name; waehrend es klingelt ein wartendes Gesicht mit dem
   Namen dessen, den man anruft. Vorher war die Flaeche in dieser
   Sekunde leer, und ein leerer Kasten mit "Es klingelt ..." sieht
   aus wie einer, der haengt -- ausgerechnet im unsichersten Moment.

Der Name kommt NICHT aus einer Abfrage. Wer anruft, bekannt beim
Start nur sich selbst; alle weiteren kommen ausschliesslich ueber das
Ereignis "dabei", und genau dort fehlte das Einsammeln. Deshalb sah
der ANRUFER unter jedem Kreis "Jemand" -- die Angerufenen nicht.

UND DIE PRUEFUNG WAR ZU NACHSICHTIG. Sie verlangte einen Namen
"laenger als ein Zeichen"; der Rueckfalltext "Jemand" ist sechs
Zeichen lang und kam damit durch. Gruen, waehrend der Hauptfall
kaputt war. Die zweite Fassung verglich gegen eine abgeschriebene
Namensliste -- die nannte "Jonas, Luna, Mara, Pat", angelegt werden
aber "Filipe, Rieke, Kessi, Tili", und der Kommentar daneben
behauptete schon da, sie sei abgeleitet. Jetzt ist sie es wirklich:
Die Namen werden beim Anlegen eingesammelt, und geprueft wird, dass
jeder GENAU DIE BEIDEN ANDEREN sieht -- nicht "irgendeinen echten
Namen", denn der eigene Name im eigenen Kasten waere auch einer.

pruef-anruf: 127 Pruefungen, 0 Fehler.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-20 22:25:45 +02:00
co-authored by Claude Opus 5
parent 9d086eccaf
commit b0e7542507
41 changed files with 812 additions and 512 deletions
+83
View File
@@ -1702,6 +1702,89 @@
.anruf__bild--ich { max-width: 88px; }
.anruf__name { font-size: .8rem; color: var(--text-leise); }
/* =====================================================================
WER IST DRAN (20.09.2026)
Filipe: „die soll auch viel besser geiler und spezieller aussehen,
aber nicht so raushängen."
„Nicht raushängen" ist dabei die schärfere der beiden Vorgaben, und
sie steht hier zuerst: Der Kasten ist 340 px breit und liegt in der
Ecke. Alles darin muss mit dieser Breite auskommen — bei einem
Gruppenanruf mit drei Leuten genauso wie bei einem Gespräch zu
zweit. Deshalb ein Raster mit `auto-fit`, das misst statt zu
rechnen, und ein Name, der umbricht statt zu schieben.
Vorher stand hier je Person das Wort „Verbunden" — bei dreien
dreimal dasselbe Wort, ohne Namen.
===================================================================== */
.anruf__leute:has(.anruf__gesicht) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
gap: 10px;
justify-items: center;
}
.anruf__gesicht {
display: flex; flex-direction: column; align-items: center; gap: 5px;
min-width: 0; max-width: 100%;
text-align: center;
}
.anruf__kreis {
display: grid; place-items: center;
width: 46px; height: 46px;
border-radius: 50%;
border: 1px solid color-mix(in srgb, var(--gut, #79d1a2) 45%, var(--rand));
background: color-mix(in srgb, var(--gut, #79d1a2) 14%, #0b1018);
color: color-mix(in srgb, var(--gut, #79d1a2) 35%, #ffffff);
font-size: .95rem; font-weight: 700;
letter-spacing: .02em;
}
/* Der Name darf umbrechen und wird notfalls gekürzt -- er ist das
Einzige hier, dessen Länge wir nicht kennen. */
.anruf__gesicht .anruf__name {
max-width: 100%;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
font-weight: 600; color: var(--text);
}
.anruf__stand { font-size: .72rem; color: var(--text-still); }
/* ---- Es klingelt noch -------------------------------------------------
EIN RUHIGER PULS, kein Blinken. Augenschonend ist hier keine
Geschmacksfrage: Der Kasten steht am Bildrand und läuft, solange das
Gespräch läuft. Ein Blinken dort wäre nach zwei Minuten
unerträglich. Deshalb ein weicher Ring, der nach außen ausläuft --
dieselbe Bewegung wie beim Punkt in der Kopfzeile. */
.anruf__gesicht[data-lage="wartet"] .anruf__kreis {
border-color: color-mix(in srgb, var(--akzent) 45%, var(--rand));
background: color-mix(in srgb, var(--akzent) 12%, #0b1018);
color: color-mix(in srgb, var(--akzent) 30%, #ffffff);
position: relative;
}
.anruf__gesicht[data-lage="wartet"] .anruf__kreis::after {
content: "";
position: absolute; inset: -5px;
border-radius: 50%;
border: 1px solid color-mix(in srgb, var(--akzent) 40%, transparent);
animation: anruf-ring 2.4s ease-out infinite;
}
@keyframes anruf-ring {
0% { transform: scale(.88); opacity: .75; }
70% { transform: scale(1.18); opacity: 0; }
100% { transform: scale(1.18); opacity: 0; }
}
/* Während es klingelt ist das Gesicht größer -- es ist in diesem
Moment das Einzige, worauf man schaut. */
.anruf__gesicht[data-lage="wartet"] .anruf__kreis { width: 58px; height: 58px; font-size: 1.1rem; }
@media (prefers-reduced-motion: reduce) {
.anruf__gesicht[data-lage="wartet"] .anruf__kreis::after { animation: none; opacity: .5; }
}
@media (forced-colors: active) {
.anruf__kreis { border: 2px solid CanvasText; }
.anruf__gesicht[data-lage="wartet"] .anruf__kreis { border-color: Highlight; }
.anruf__gesicht[data-lage="wartet"] .anruf__kreis::after { display: none; }
}
.anruf__knoepfe { display: flex; flex-wrap: wrap; gap: 8px; }
.anruf__knopf {
min-height: 40px;
+139 -7
View File
@@ -251,6 +251,82 @@
* klingelt …" -- man sah nicht, wen man anruft. */
const wer = (text) => { const w = $('anruf-wer'); if (w) w.textContent = text; };
/* =====================================================================
WER IST DRAN -- MIT NAMEN UND GESICHT (20.09.2026)
Filipe, zur Anrufbox: „die soll auch viel besser geiler und
spezieller aussehen, aber nicht so raushaengen."
DER GROESSTE MANGEL WAR KEIN SCHOENHEITSFEHLER. In einem Anruf
ohne Video stand unter dem Kasten das Wort „Verbunden" -- einmal
je Person. Bei dreien stand es dreimal, und man sah nicht, WER
verbunden war. In einer Gruppe ist genau das die Frage: Ist Luna
schon drin? Hoert der Modi mit?
Es gibt keine Profilbilder im Anruf -- die Sitzung liefert sie
nicht mit, und sie dafuer durch eine Abfrage zu holen, die auf
JEDER Seite laeuft, waere an der falschen Stelle gespart und an
der falschen teuer. Stattdessen dasselbe Mittel wie im Chat: der
Anfangsbuchstabe in einem Kreis. Er ist sofort da, kostet nichts
und unterscheidet in einer Gruppe von drei bis fuenf Leuten
zuverlaessig.
WAEHREND ES KLINGELT steht dort, WEN man anruft -- gross, mit
einem ruhigen Puls. Vorher war die Flaeche in diesem Moment leer,
und ein leerer Kasten mit „Es klingelt ..." sieht aus wie einer,
der haengt. Es ist die Sekunde, in der man am unsichersten ist.
===================================================================== */
/** Die Namen aller, die im Anruf sind -- id -> Name.
*
* Auf dem Anruf selbst und nicht daneben: Endet er, ist die Karte
* mit ihm weg. Eine Karte, die einen Anruf ueberlebt, zeigt beim
* naechsten Namen von gestern. */
function namenMerken(liste) {
if (!anruf || !Array.isArray(liste)) return;
if (!anruf.namen) anruf.namen = new Map();
for (const w of liste) if (w && w.id != null) anruf.namen.set(w.id, w.name || '');
}
/** Ein bis zwei Buchstaben aus einem Namen -- dieselbe Regel wie im
* Chat: bei zwei Wörtern die Anfangsbuchstaben beider, sonst der
* erste. „Luna Sanchez" -> LS, „Dogi" -> D. */
function zeichenVon(name) {
const teile = String(name || '').trim().split(/\s+/).filter(Boolean);
if (!teile.length) return '?';
if (teile.length === 1) return teile[0][0].toUpperCase();
return (teile[0][0] + teile[teile.length - 1][0]).toUpperCase();
}
/** Ein Gesichtsfeld: Kreis mit Buchstaben, Name darunter.
*
* `lage` ist 'da' (verbunden) oder 'wartet' (klingelt noch) -- das
* Stilblatt macht daraus den Unterschied zwischen ruhig und
* pulsierend. Der Zustand steht auch im Text darunter: Ein Puls
* allein ist nichts, was ein Vorleseprogramm ansagen kann. */
function gesichtsfeld(name, lage, stumm) {
const k = el('div', 'anruf__gesicht');
k.dataset.lage = lage;
const kreis = el('span', 'anruf__kreis', zeichenVon(name));
/* Der Kreis traegt zwei Buchstaben -- als Bild, nicht als Text.
Ein Vorleseprogramm soll „Luna Sanchez, klingelt" sagen und
nicht „L S". */
kreis.setAttribute('aria-hidden', 'true');
k.append(kreis);
if (stumm) {
/* KEINE WIEDERHOLUNG DESSEN, WAS DIREKT DARUEBER STEHT.
Beim Anruf zu zweit sagt die Kopfzeile schon „Luna Sanchez" und
„Es klingelt ..." -- beides zwei Zeilen tiefer noch einmal
hinzuschreiben macht den Kasten hoeher und sagt nichts dazu.
Fuer Vorleseprogramme bleibt der Name trotzdem am Element. */
k.setAttribute('aria-label', `${name || 'Jemand'} – klingelt`);
return k;
}
k.append(el('span', 'anruf__name', name || 'Jemand'));
k.append(el('span', 'anruf__stand', lage === 'da' ? 'ist dabei' : 'klingelt …'));
return k;
}
/** Einen Schalter auf an/aus stellen.
*
* Die Aufschrift bleibt IMMER dieselbe (die Sache), der Zustand
@@ -408,13 +484,30 @@
}
async function starten(raumId, mitVideo, raumName) {
/* WEN RUFE ICH AN? Der Chat weiss es und gibt es mit. Faellt
der Name aus (aelterer Aufruf), bleibt es bei „Anruf" --
das ist immer noch richtig, nur weniger. */
wer(raumName || 'Anruf');
if (anruf) return;
kastenBauen();
$('anruf').hidden = false;
/* WEN RUFE ICH AN? Der Chat weiss es und gibt es mit. Faellt
der Name aus (aelterer Aufruf), bleibt es bei „Anruf" --
das ist immer noch richtig, nur weniger.
NACH `kastenBauen()`, UND DAS IST DER PUNKT (20.09.2026):
Vorher stand diese Zeile DAVOR. Da gibt es das Element
`#anruf-wer` noch gar nicht -- `wer()` fand nichts, tat nichts,
und der Name war weg. Oben stand deshalb bei JEDEM ersten Anruf
„Anruf" statt „Luna Sanchez", obwohl der Chat ihn korrekt
mitgab. Der Kommentar daneben beschrieb die Absicht und nicht,
was passierte -- ein Kommentar, der eine Absicht beschreibt,
erfuellt sie nicht.
Gefunden hat es ein Bildschirmfoto, nicht das Lesen: Der Code
liest sich richtig, und der Rueckfall „Anruf" ist plausibel
genug, um nicht aufzufallen.
Der frueher hier stehende `if (anruf) return;` ist mit nach oben
gewandert. Das ist auch sachlich besser: Laeuft schon ein
Gespraech, soll ein zweiter Aufruf es nicht umbenennen. */
wer(raumName || 'Anruf');
/* JEDES GESPRAECH FAENGT LEISE AN (20.09.2026) -- an BEIDEN
Wegen, Anrufen und Rangehen. Einer allein waere die
Haelfte, und die andere Haelfte faellt niemandem auf,
@@ -449,6 +542,10 @@
$('anruf-video').hidden = !mitVideo;
if (mitVideo) $('anruf-video').setAttribute('aria-pressed', 'true');
lage('Es klingelt …');
/* JETZT ZEICHNEN, nicht erst wenn jemand rangeht: Dies ist der
Moment, in dem die Flaeche bisher leer war -- und ein leerer
Kasten mit „Es klingelt ..." sieht aus wie einer, der haengt. */
leuteZeichnen();
/* DAS FREIZEICHEN. Ein stiller Kasten mit laufender Uhr sagt nur,
dass etwas passiert -- nicht, was. */
freiTon();
@@ -528,6 +625,7 @@
/* Mit allen, die schon dabei sind, eine Verbindung aufbauen --
aber das ANGEBOT macht nur, wer die kleinere Nummer hat. */
namenMerken(d.wer);
for (const p of d.wer || []) {
if (p.id === ichId) continue;
if (ichId < p.id) angebotMachen(p.id);
@@ -580,20 +678,38 @@
eigen.srcObject = anruf.eigener;
ziel.append(eigen);
}
for (const [wer, strom] of anruf.stroeme) {
for (const [werId, strom] of anruf.stroeme) {
if (anruf.video) {
const v = el('video', 'anruf__bild');
v.autoplay = true; v.playsInline = true;
v.srcObject = strom;
ziel.append(v);
} else {
/* Der Ton muss abgespielt werden -- das Element bleibt, nur
unsichtbar. Es traegt die Verbindung, nicht die Anzeige. */
const t = el('audio');
t.autoplay = true;
t.srcObject = strom;
ziel.append(t);
ziel.append(el('span', 'anruf__name', 'Verbunden'));
/* HIER STAND „Verbunden" -- einmal je Person, ohne Namen.
Bei dreien dreimal dasselbe Wort, und man sah nicht, wer. */
ziel.append(gesichtsfeld(anruf.namen?.get(werId), 'da'));
}
}
/* ---- NOCH NIEMAND DRAN: zeigen, WEN man anruft ----------------
Die Flaeche war in diesem Moment leer, und ein leerer Kasten mit
„Es klingelt ..." sieht aus wie einer, der haengt -- ausgerechnet
in der Sekunde, in der man am unsichersten ist. */
if (!anruf.video && !anruf.stroeme.size) {
const gegenueber = $('anruf-wer')?.textContent?.trim();
/* „Anruf" ist der Rueckfalltext, kein Name -- dann lieber gar
kein Gesicht als ein Kreis mit „A" darin. */
if (gegenueber && gegenueber !== 'Anruf') {
/* `true`: Name und Zustand stehen schon in der Kopfzeile --
siehe die Begruendung in `gesichtsfeld`. */
ziel.append(gesichtsfeld(gegenueber, 'wartet', true));
}
void wer;
}
/* Der Zustand gilt auch fuer die Elemente, die es gerade eben noch
nicht gab -- und die Geraeteauswahl kann erst hier entstehen,
@@ -975,6 +1091,7 @@
if (!d.laeuft) { nachfrageStoppen(); tonAus(); lage('Der Anruf ist vorbei.');
setTimeout(() => { if (anruf) auflegen(); }, 1500); return; }
namenMerken(d.wer);
const andere = (d.wer || []).filter((w) => w.id !== ichId);
if (!andere.length) return; /* es klingelt weiter, alles richtig */
@@ -1048,6 +1165,21 @@
Den richtigen Zeitpunkt kennt nur die Verbindung selbst --
`onconnectionstatechange` meldet ihn (siehe oben). */
lage('Baut auf …');
/* DEN NAMEN MITNEHMEN (20.09.2026).
Hier fehlte er, und das Loch war genau umgekehrt zu dem, was
man erwartet: Wer ANGERUFEN wird, erfaehrt die Namen ueber
`dazustossen` (die Antwort des Servers enthaelt alle). Wer
ANRUFT, bekommt beim Start nur sich selbst -- alle weiteren
kommen ausschliesslich ueber dieses Ereignis. Ohne diese Zeile
stand bei ihm unter jedem Kreis „Jemand", und ausgerechnet der
Anrufer sah als Einziger keine Namen.
Gefunden hat es die Pruefung, aber erst, nachdem sie
verschaerft wurde: Der erste Anlauf verlangte nur „Name laenger
als ein Zeichen" -- und „Jemand" ist sechs Zeichen lang. Eine
Pruefung, die den Rueckfalltext durchlaesst, prueft nichts. */
namenMerken([d.person]);
/* Der Neue: Angebot macht, wer die kleinere Nummer hat. */
if (ichId < d.person.id) angebotMachen(d.person.id);
else verbindungFuer(d.person.id);