Der Treff sagt jetzt, ob gerade gestreamt wird
Filipe zu dieser Seite: "mach was anderes daraus oder mach es viel krass und geiler ... erstell was was mich von den socken schmeisst. es soll wirklich sogar ein wow effekt fuer die community ergeben." Dazu die eine Auflage: die Kachel bleibt. WAS SIE WAR: zwei Karten mit zwei Adressen. Sauber gebaut, richtig beschriftet -- und niemand kam ein zweites Mal wieder. Wer im Treff ist, kennt die Website. Den Rabattcode merkt man sich beim ersten Mal. DIE FRAGE, DIE WIRKLICH JEMAND HAT, IST EINE ANDERE: Laeuft er gerade? Und wenn nicht -- wie lange noch? Die Antwort kennt das Haus seit dem 20.08.: postfach.dogfather-universe.com/live-status, derselbe Dienst, von dem der oeffentliche Streamplan lebt. Im Treff gab es sie bisher nicht. Jetzt steht oben ein Puls: ein Ring, der sich ueber den Tag bis zur naechsten Sendezeit fuellt, darin der Countdown auf die Sekunde. Sendet er, wird die Karte rot, atmet im Ruhepuls und traegt den Titel des Streams plus einen Weg hin. Darunter die drei Accounts und -- wenn die Verwaltung eines eintraegt -- das besondere Event mit eigener Uhr. DREI AUSGAENGE, NICHT ZWEI. Das ist der eigentliche Unterschied zur oeffentlichen Seite: streamplan.js faengt dort jeden Fehler ab und setzt `istLive = false`. Aus "wir konnten nicht fragen" wird "er sendet nicht" -- und wer das liest, macht die Seite zu und verpasst den Stream. Der Dienst liefert `autoHealthy` sogar mit; gelesen wird es draussen nicht. Hier schon: Bei einer Stoerung steht "Konnten wir gerade nicht nachsehen", dazu der Hinweis, dass das NICHT heisst, dass kein Stream laeuft. Der Countdown laeuft dabei weiter -- er braucht kein Netz. NICHTS IST ABGESCHRIEBEN. Die drei Accounts kommen ueber /workspace/api/draussen aus derselben KANAELE-Liste, an der die Aufgabenverteilung haengt; die Adressen baut der Steckbrief. Die Sendezeit steht im Server, und pruef-draussen haelt sie gegen FIX_STUNDE in streamplan.js -- verschiebt jemand den Termin und aendert nur einen Ort, wird die Pruefung rot. Gemessen, bevor gebaut wurde: der Dienst antwortet und meldet sich gesund, CORS steht auf *, und `connect-src` der Treff-Seiten fuehrt postfach bereits. Ohne das Letzte waere der Abruf still blockiert worden und haette ausgesehen wie ein toter Dienst. Die Kachel heisst jetzt "Draussen" statt "Unsere Seiten" -- drei Dinge statt zwei Adressen. Ziel und Datei bleiben gleich. pruef-wege-nach-draussen schreibt den Namen nicht mehr ab, sondern verlangt, dass Kachel und Seitenueberschrift uebereinstimmen. Zwei Namen fuer denselben Ort waren schon einmal ein echter Fehler. Nebenbefund, dort gleich behoben: Dieselbe Pruefung verlangte, dass die Community-Kacheln restlos in Dreierreihen aufgehen -- obwohl ihr eigener Kommentar sagt, eine freie Zelle am Ende sei erlaubt. Sie war rot bei einwandfreiem Raster. Gemessen wird jetzt, was wirklich schiefgeht: eine doppelt breite Kachel, die in der letzten Spalte beginnt und die Zelle davor leer laesst. Mit Gegenprobe. pruef-draussen: 39 Pruefungen, alle vier Lagen im echten Browser -- die Stoerung wird dafuer absichtlich hergestellt. pruef-wege-nach-draussen: 67 statt 66, keine Fehler mehr. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -425,3 +425,373 @@
|
||||
.aussen:hover,
|
||||
.aussen:focus-within { transform: none; }
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
DER PULS, DAS EVENT UND DIE KANAELE (21.09.2026)
|
||||
|
||||
Filipe wollte aus dieser Seite etwas machen, das "von den Socken
|
||||
haut". Der Weg dahin war NICHT mehr Effekt, sondern eine Frage, die
|
||||
vorher niemand beantwortet hat: Laeuft er gerade?
|
||||
|
||||
DESHALB LIEGT DER GANZE AUFWAND OBEN. Der Puls ist das Erste und
|
||||
Groesste auf der Seite, und er ist das Einzige, was sich bewegt --
|
||||
und zwar nur dann, wenn die Bewegung etwas bedeutet (er sendet).
|
||||
Bewegung, die immer laeuft, ist Tapete; Bewegung, die genau einmal
|
||||
im Tag anspringt, ist ein Signal.
|
||||
|
||||
AUGENSCHONEND WIE ALLES IM HAUS: Der Ton liegt im Verlauf, im Rand
|
||||
und im Ring -- nie als leuchtende Flaeche hinter Text. Das Rot des
|
||||
Live-Zustands ist ein gedecktes Korallrot (#e8736b), kein
|
||||
Signalrot: Auf einem dunklen Bildschirm ist reines Rot nach zwei
|
||||
Minuten unangenehm, und diese Seite soll man waehrend des Streams
|
||||
offen lassen koennen.
|
||||
|
||||
`prefers-reduced-motion` schaltet jede Bewegung ab. Der Zustand
|
||||
bleibt trotzdem erkennbar -- er steht in Farbe UND in Worten, nicht
|
||||
nur im Puls. Eine Auskunft, die ausschliesslich an einer Animation
|
||||
haengt, kommt bei einem Teil der Leute gar nicht an.
|
||||
===================================================================== */
|
||||
|
||||
.puls {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.25rem;
|
||||
margin: 1.5rem 0 0;
|
||||
padding: 1.35rem 1.4rem;
|
||||
border-radius: 1.1rem;
|
||||
border: 1px solid color-mix(in srgb, var(--puls-ton) 42%, transparent);
|
||||
background:
|
||||
radial-gradient(120% 95% at 6% -5%,
|
||||
color-mix(in srgb, var(--puls-ton) 26%, transparent), transparent 58%),
|
||||
radial-gradient(90% 80% at 105% 110%,
|
||||
color-mix(in srgb, var(--puls-ton) 15%, transparent), transparent 62%),
|
||||
linear-gradient(158deg,
|
||||
color-mix(in srgb, var(--puls-ton) 13%, #0c1420),
|
||||
#090f1a 72%);
|
||||
box-shadow:
|
||||
0 18px 38px -20px color-mix(in srgb, var(--puls-ton) 85%, transparent),
|
||||
inset 0 1px 0 color-mix(in srgb, var(--puls-ton) 22%, transparent);
|
||||
transition: border-color .4s ease, box-shadow .4s ease;
|
||||
}
|
||||
|
||||
/* DIE VIER LAGEN. Jede hat ihren Ton, und der Ton ist nie die einzige
|
||||
Auskunft -- daneben steht immer ein Satz. Wer Farben nicht
|
||||
unterscheidet, liest trotzdem, was los ist. */
|
||||
.puls { --puls-ton: #7ec8f2; } /* laedt / wartet */
|
||||
.puls[data-lage="live"] { --puls-ton: #e8736b; } /* er sendet */
|
||||
.puls[data-lage="unklar"] { --puls-ton: #d9a441; } /* wir wissen es nicht */
|
||||
|
||||
/* Die Aura liegt UNTER allem und traegt die einzige Bewegung der
|
||||
Seite. Sie ist nur im Live-Zustand sichtbar. */
|
||||
.puls__aura {
|
||||
position: absolute;
|
||||
inset: -40%;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
background: radial-gradient(closest-side,
|
||||
color-mix(in srgb, var(--puls-ton) 30%, transparent), transparent 70%);
|
||||
transition: opacity .6s ease;
|
||||
}
|
||||
.puls[data-lage="live"] .puls__aura {
|
||||
opacity: 1;
|
||||
animation: puls-atmen 3.4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* 3,4 SEKUNDEN, NICHT EINE. Ein schneller Puls wirkt wie eine Warnung
|
||||
und macht nervoes; dieser hier atmet ungefaehr im Ruhepuls eines
|
||||
Menschen. Die Deckkraft schwankt zwischen 55 und 100 Prozent --
|
||||
sichtbar, aber nicht blinkend. */
|
||||
@keyframes puls-atmen {
|
||||
0%, 100% { opacity: .55; transform: scale(1); }
|
||||
50% { opacity: 1; transform: scale(1.06); }
|
||||
}
|
||||
|
||||
.puls__ring-feld {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex: 0 0 auto;
|
||||
width: 5.6rem;
|
||||
height: 5.6rem;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.puls__ring {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* Oben anfangen statt rechts -- so liest sich der Bogen wie eine
|
||||
Uhr und nicht wie ein Tachometer. */
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
.puls__ring-bahn {
|
||||
fill: none;
|
||||
stroke: color-mix(in srgb, var(--puls-ton) 18%, transparent);
|
||||
stroke-width: 6;
|
||||
}
|
||||
.puls__ring-fortschritt {
|
||||
fill: none;
|
||||
stroke: var(--puls-ton);
|
||||
stroke-width: 6;
|
||||
stroke-linecap: round;
|
||||
/* Der Umfang wird im Skript gesetzt -- aus dem Radius gerechnet,
|
||||
damit eine Aenderung am SVG nicht hier nachgezogen werden muss. */
|
||||
transition: stroke-dashoffset .6s ease;
|
||||
}
|
||||
|
||||
.puls__punkt {
|
||||
position: absolute;
|
||||
width: .85rem;
|
||||
height: .85rem;
|
||||
border-radius: 50%;
|
||||
background: var(--puls-ton);
|
||||
opacity: .9;
|
||||
}
|
||||
.puls[data-lage="live"] .puls__punkt {
|
||||
animation: puls-punkt 3.4s ease-in-out infinite;
|
||||
}
|
||||
@keyframes puls-punkt {
|
||||
0%, 100% { transform: scale(1); opacity: .75; }
|
||||
50% { transform: scale(1.35); opacity: 1; }
|
||||
}
|
||||
|
||||
.puls__text {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .2rem;
|
||||
}
|
||||
|
||||
.puls__marke {
|
||||
margin: 0;
|
||||
font-size: .78rem;
|
||||
letter-spacing: .08em;
|
||||
text-transform: uppercase;
|
||||
color: color-mix(in srgb, var(--puls-ton) 70%, #cfe0f0);
|
||||
}
|
||||
|
||||
.puls__lage {
|
||||
margin: 0;
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
color: #eaf3fb;
|
||||
}
|
||||
|
||||
/* DIE ZAHL IST DAS GROESSTE AUF DER SEITE -- sie ist der Grund, warum
|
||||
man wiederkommt. `tabular-nums` verhindert das Zappeln, das sonst
|
||||
jede Sekunde entsteht, wenn eine 1 schmaler ist als eine 8. */
|
||||
.puls__zahl {
|
||||
margin: .1rem 0 0;
|
||||
font-size: clamp(1.8rem, 6vw, 2.6rem);
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: .01em;
|
||||
color: #f4f9ff;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
/* Im Live-Zustand steht dort der Titel des Streams, kein Countdown --
|
||||
ein Satz in Countdown-Groesse wuerde umbrechen und die Karte
|
||||
aufreissen. */
|
||||
.puls[data-lage="live"] .puls__zahl {
|
||||
font-size: clamp(1.1rem, 3.4vw, 1.45rem);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.puls__klein {
|
||||
margin: .25rem 0 0;
|
||||
font-size: .82rem;
|
||||
line-height: 1.45;
|
||||
color: #9fb4c8;
|
||||
}
|
||||
|
||||
.puls__knopf {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
/* Rechts anschlagen, solange die Karte eine Zeile ist. Bricht sie um,
|
||||
hebt `margin-left: 0` unten die Regel wieder auf. */
|
||||
margin-left: auto;
|
||||
flex: 0 0 auto;
|
||||
/* 44 px ist die Grenze im Haus -- am Daumen, nicht an der Maus
|
||||
gemessen. */
|
||||
min-height: 44px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: .55rem 1.05rem;
|
||||
border-radius: .7rem;
|
||||
border: 1px solid color-mix(in srgb, var(--puls-ton) 55%, transparent);
|
||||
background: color-mix(in srgb, var(--puls-ton) 18%, transparent);
|
||||
color: #f4f9ff;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
transition: background .2s ease, border-color .2s ease, transform .2s ease;
|
||||
}
|
||||
.puls__knopf:hover {
|
||||
background: color-mix(in srgb, var(--puls-ton) 28%, transparent);
|
||||
border-color: color-mix(in srgb, var(--puls-ton) 75%, transparent);
|
||||
}
|
||||
.puls__knopf:focus-visible {
|
||||
outline: 2px solid var(--puls-ton);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* AM HANDY UNTEREINANDER. Keine Schwelle in Pixeln, sondern die
|
||||
Frage, ob der Text neben dem Ring noch lesbar ist: Unter 34rem
|
||||
bleiben fuer ihn weniger als 20 Zeichen je Zeile. */
|
||||
@media (max-width: 34rem) {
|
||||
.puls { flex-direction: column; align-items: flex-start; gap: .9rem; }
|
||||
.puls__ring-feld { width: 4.6rem; height: 4.6rem; }
|
||||
/* In einer Spalte gibt es kein "rechts" -- der Knopf stellt sich
|
||||
unter den Text und nimmt die volle Breite, damit er am Daumen
|
||||
nicht gesucht werden muss. */
|
||||
.puls__knopf { margin-left: 0; width: 100%; justify-content: center; }
|
||||
}
|
||||
|
||||
/* Der Textblock nimmt den Platz, der zwischen Ring und Knopf uebrig
|
||||
bleibt -- sonst draengt sich alles links zusammen. */
|
||||
.puls__text { flex: 1 1 auto; }
|
||||
|
||||
/* ---- DAS BESONDERE EVENT ------------------------------------------- */
|
||||
|
||||
.event {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin: .9rem 0 0;
|
||||
padding: 1rem 1.2rem;
|
||||
border-radius: 1rem;
|
||||
--event-ton: #d49ae8;
|
||||
border: 1px solid color-mix(in srgb, var(--event-ton) 45%, transparent);
|
||||
background: linear-gradient(152deg,
|
||||
color-mix(in srgb, var(--event-ton) 16%, #0c1420), #090f1a 78%);
|
||||
}
|
||||
.event[hidden] { display: none; }
|
||||
|
||||
.event__zeichen {
|
||||
flex: 0 0 auto;
|
||||
font-size: 1.5rem;
|
||||
color: var(--event-ton);
|
||||
line-height: 1;
|
||||
}
|
||||
.event__text { min-width: 0; }
|
||||
.event__marke {
|
||||
margin: 0;
|
||||
font-size: .74rem;
|
||||
letter-spacing: .08em;
|
||||
text-transform: uppercase;
|
||||
color: color-mix(in srgb, var(--event-ton) 75%, #cfe0f0);
|
||||
}
|
||||
.event__titel {
|
||||
margin: .15rem 0 0;
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
color: #eaf3fb;
|
||||
}
|
||||
.event__zahl {
|
||||
margin: .2rem 0 0;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: .92rem;
|
||||
color: #b9c9da;
|
||||
}
|
||||
.event[data-jetzt="ja"] .event__zahl {
|
||||
color: #f4f9ff;
|
||||
font-weight: 700;
|
||||
}
|
||||
.event__knopf {
|
||||
margin-left: auto;
|
||||
min-height: 44px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: .5rem 1rem;
|
||||
border-radius: .7rem;
|
||||
border: 1px solid color-mix(in srgb, var(--event-ton) 55%, transparent);
|
||||
background: color-mix(in srgb, var(--event-ton) 16%, transparent);
|
||||
color: #f4f9ff;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.event__knopf:focus-visible { outline: 2px solid var(--event-ton); outline-offset: 2px; }
|
||||
|
||||
@media (max-width: 34rem) {
|
||||
.event { flex-wrap: wrap; }
|
||||
.event__knopf { margin-left: 0; width: 100%; justify-content: center; }
|
||||
}
|
||||
|
||||
/* ---- DIE KANAELE ---------------------------------------------------- */
|
||||
|
||||
.kanaele { margin: 1.6rem 0 0; }
|
||||
.kanaele[hidden] { display: none; }
|
||||
|
||||
.kanaele__titel,
|
||||
.seiten-titel {
|
||||
margin: 0 0 .7rem;
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
color: #cfe0f0;
|
||||
}
|
||||
.seiten-titel { margin-top: 1.8rem; }
|
||||
|
||||
.kanaele__reihe {
|
||||
display: grid;
|
||||
gap: .8rem;
|
||||
/* Dieselbe Regel wie beim Kartengitter darunter: eine Regel statt
|
||||
einer Zahl. Ein vierter Kanal ordnet sich von selbst ein. */
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
|
||||
}
|
||||
|
||||
.kanal {
|
||||
--kanal-ton: #7ec8f2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .15rem;
|
||||
padding: .85rem 1rem;
|
||||
border-radius: .9rem;
|
||||
border: 1px solid color-mix(in srgb, var(--kanal-ton) 30%, transparent);
|
||||
background: linear-gradient(150deg,
|
||||
color-mix(in srgb, var(--kanal-ton) 11%, #0c1420), #090f1a 80%);
|
||||
text-decoration: none;
|
||||
transition: transform .2s ease, border-color .2s ease, background .2s ease;
|
||||
}
|
||||
.kanal:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: color-mix(in srgb, var(--kanal-ton) 58%, transparent);
|
||||
}
|
||||
.kanal:focus-visible { outline: 2px solid var(--kanal-ton); outline-offset: 2px; }
|
||||
|
||||
.kanal__name { font-weight: 600; color: #eaf3fb; }
|
||||
.kanal__handle {
|
||||
font-size: .85rem;
|
||||
color: color-mix(in srgb, var(--kanal-ton) 78%, #cfe0f0);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.kanal__satz { margin-top: .2rem; font-size: .84rem; line-height: 1.45; color: #9fb4c8; }
|
||||
|
||||
.kanaele__fehler {
|
||||
margin: 0;
|
||||
padding: .8rem 1rem;
|
||||
border-radius: .8rem;
|
||||
border: 1px solid rgba(217, 164, 65, .35);
|
||||
background: rgba(217, 164, 65, .08);
|
||||
font-size: .88rem;
|
||||
line-height: 1.5;
|
||||
color: #d8c49a;
|
||||
}
|
||||
.kanaele__fehler[hidden] { display: none; }
|
||||
|
||||
/* ---- WER BEWEGUNG NICHT MOECHTE ------------------------------------- */
|
||||
|
||||
/* Der Zustand bleibt vollstaendig lesbar: Farbe, Ring und Text stehen
|
||||
weiter da. Es hoert nur das Atmen auf. */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.puls[data-lage="live"] .puls__aura,
|
||||
.puls[data-lage="live"] .puls__punkt { animation: none; }
|
||||
.puls__ring-fortschritt { transition: none; }
|
||||
.kanal { transition: none; }
|
||||
.kanal:hover { transform: none; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user