Overlay: fuenf Browser-Sources fuer OBS
Einblendungen, Chat, Statusleiste, Effekte, Pausenbild. Kein Build, kein Framework - OBS laedt die Dateien direkt, und jede Zwischenstufe waere ein Ding, das mitten im Stream kaputtgehen kann. Server-Sent Events statt WebSocket, entgegen der Skizze in chat-control.md: Node bringt einen WebSocket-*Client* mit, aber keinen Server, `EventSource` verbindet sich von allein neu, und die Richtung stimmt - das Overlay hoert nur zu. Gegen die tote Browser-Source nach einem Neustart stehen drei Dinge: der eingebaute Reconnect, ein Herzschlag als echtes Ereignis (ein SSE-Kommentar haelt die Verbindung offen, ist im Browser aber unsichtbar), und ein Zustands-Schnappschuss beim Verbinden. Abos, Raids, Bits und Loeschungen kommen ueber IRC-USERNOTICE und CLEARCHAT herein; das wurde bisher weggeworfen. Follows fehlen weiter, die gibt es nur ueber EventSub. GIFs im Chat: Twitch schickt dafuer einen Tag `gifs`, den wir nicht kannten. gifs=0-22|Pnd7qLFuGCOntZcNKB|https://media0.giphy.com/media/.../giphy.gif PRIVMSG #abdulive_ :[Idk GIF by Respective] Der Nachrichtentext ist nur ein Platzhalter, der Bereich markiert ihn. Ausgewertet wird er jetzt; ohne das stand woertlich "[Idk GIF by Respective]" im Bild. Gefunden wurde der Tag durch einen Rohmitschnitt - in 3094 Nachrichten aus acht grossen Kanaelen war kein einziges GIF, weil die Adresse eben nicht im Text steht. Zwei Fehler, die beim Durchklicken im Browser aufgefallen sind: - Die Aufraeumfunktion im Chat zaehlte Zeilen, die erst nach der Ausblend-Animation aus dem DOM verschwinden, und lief in eine Endlosschleife. In OBS waere die Quelle beim vierzehnten Chatbeitrag eingefroren. - Der Bereichsparser der GIFs war zu nachsichtig: `-1-4` zerfaellt beim Trennen in ['', '1', '4'], und `Number('')` ist 0 - der Bereich saehe gueltig aus und wuerde den falschen Textteil ersetzen. Auf der Uebersichtsseite steht fuer jede Einblendung, jede Chat-Variante und jeden Effekt ein Knopf. Ohne den liesse sich eine Raid-Einblendung nur pruefen, indem man auf einen echten Raid wartet. Was einen Zustand setzt, ist waehrend eines Streams gesperrt; "Stand zuruecksetzen" ausdruecklich nicht, sonst kaeme man aus einem erfundenen Zustand nicht mehr heraus. build-package.ts nimmt jetzt auch html/css/js mit - sonst liefert das Paket 404 statt Overlay. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
3b606e36fb
commit
bc2c8f6cbe
24 files changed
+3221
-8
No files matched your search
@@ -0,0 +1,156 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>abdulive - Pausenbild</title>
|
||||
<link rel="stylesheet" href="/theme.css">
|
||||
<style>
|
||||
/* Das einzige Overlay mit deckendem Hintergrund: es ersetzt das Bild, statt
|
||||
* darueber zu liegen. Gedacht als eigene OBS-Szene fuer "gleich geht es los",
|
||||
* Pause und Ende. Text ueber ?text= und ?unter=. */
|
||||
body { background: #0a0813; }
|
||||
|
||||
#grund {
|
||||
position: fixed;
|
||||
inset: -20%;
|
||||
background:
|
||||
radial-gradient(42% 42% at 22% 28%, rgba(169, 112, 255, .55), transparent 70%),
|
||||
radial-gradient(38% 38% at 78% 32%, rgba(79, 209, 197, .45), transparent 70%),
|
||||
radial-gradient(45% 45% at 55% 82%, rgba(255, 79, 163, .40), transparent 70%);
|
||||
filter: blur(48px);
|
||||
animation: treiben 28s ease-in-out infinite alternate;
|
||||
}
|
||||
@keyframes treiben {
|
||||
0% { transform: translate(0, 0) scale(1) rotate(0deg); }
|
||||
50% { transform: translate(-3%, 2.5%) scale(1.1) rotate(4deg); }
|
||||
100% { transform: translate(3%, -2%) scale(1.04) rotate(-3deg); }
|
||||
}
|
||||
|
||||
/* Feines Raster darueber: nimmt dem Verlauf das Billige und gibt dem Bild
|
||||
* eine Struktur, die auch nach der Twitch-Kompression noch da ist. */
|
||||
#raster {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(255, 255, 255, .045) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255, 255, 255, .045) 1px, transparent 1px);
|
||||
background-size: 64px 64px;
|
||||
mask-image: radial-gradient(70% 70% at 50% 50%, #000, transparent);
|
||||
}
|
||||
|
||||
#mitte {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
justify-items: center;
|
||||
text-align: center;
|
||||
gap: 26px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: clamp(64px, 8.5vw, 148px);
|
||||
font-weight: 800;
|
||||
letter-spacing: -.035em;
|
||||
/* Unterlaengen brauchen Platz. `background-clip: text` faerbt nur, was
|
||||
* innerhalb der Polsterbox liegt - was darueber hinausragt, bleibt
|
||||
* durchsichtig und sieht abgeschnitten aus. Bei fetter Schrift in dieser
|
||||
* Groesse reicht eine knappe Zeilenhoehe dafuer nicht.
|
||||
*
|
||||
* Nachgemessen am "g" in "Gleich geht es los": mit 1.04 und .06em fehlte
|
||||
* noch ein Stueck vom Bogen. */
|
||||
line-height: 1.18;
|
||||
padding-bottom: .14em;
|
||||
background: linear-gradient(100deg, #fff 18%, var(--lila) 48%, var(--tuerkis) 78%, #fff 96%);
|
||||
background-size: 260% 100%;
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
animation: schimmern 7s linear infinite;
|
||||
filter: drop-shadow(0 10px 40px rgba(0, 0, 0, .55));
|
||||
}
|
||||
@keyframes schimmern { to { background-position: 260% 0; } }
|
||||
|
||||
#unter {
|
||||
font-size: clamp(20px, 2vw, 30px);
|
||||
color: var(--schrift-leise);
|
||||
max-width: 34ch;
|
||||
}
|
||||
|
||||
/* Der Taktgeber: fuenf Balken, die atmen. Ohne bewegtes Element wirkt ein
|
||||
* Standbild wie ein eingefrorener Stream, und Zuschauer laden neu. */
|
||||
#takt { display: flex; gap: 12px; align-items: flex-end; height: 68px; margin-top: 10px; }
|
||||
#takt i {
|
||||
display: block;
|
||||
width: 13px;
|
||||
border-radius: 5px;
|
||||
background: linear-gradient(180deg, var(--tuerkis), var(--lila));
|
||||
animation: atmen 1.5s ease-in-out infinite;
|
||||
}
|
||||
#takt i:nth-child(1) { height: 40%; animation-delay: 0s; }
|
||||
#takt i:nth-child(2) { height: 75%; animation-delay: .18s; }
|
||||
#takt i:nth-child(3) { height: 100%; animation-delay: .36s; }
|
||||
#takt i:nth-child(4) { height: 65%; animation-delay: .54s; }
|
||||
#takt i:nth-child(5) { height: 35%; animation-delay: .72s; }
|
||||
@keyframes atmen {
|
||||
0%, 100% { transform: scaleY(.35); opacity: .6; }
|
||||
50% { transform: scaleY(1); opacity: 1; }
|
||||
}
|
||||
|
||||
#fuss {
|
||||
position: fixed;
|
||||
left: 0; right: 0; bottom: 42px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 44px;
|
||||
font-size: 22px;
|
||||
color: var(--schrift-leise);
|
||||
}
|
||||
#fuss b { color: var(--schrift); font-weight: 600; }
|
||||
#uhr { font-variant-numeric: tabular-nums; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="grund"></div>
|
||||
<div id="raster"></div>
|
||||
<div id="mitte">
|
||||
<h1 id="gross">Gleich geht es los</h1>
|
||||
<div id="unter"></div>
|
||||
<div id="takt"><i></i><i></i><i></i><i></i><i></i></div>
|
||||
</div>
|
||||
<div id="fuss">
|
||||
<span id="uhr"></span>
|
||||
<span>twitch.tv/<b id="kanal">abdulive_</b></span>
|
||||
<span id="thema"></span>
|
||||
</div>
|
||||
|
||||
<script src="/client.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
document.getElementById('gross').textContent = Overlay.param('text', 'Gleich geht es los');
|
||||
document.getElementById('unter').textContent = Overlay.param('unter', '');
|
||||
document.getElementById('kanal').textContent = Overlay.param('kanal', 'abdulive_');
|
||||
|
||||
var uhr = document.getElementById('uhr');
|
||||
function zeit() {
|
||||
uhr.textContent = new Date().toLocaleTimeString('de-DE',
|
||||
{ hour: '2-digit', minute: '2-digit' });
|
||||
}
|
||||
zeit();
|
||||
setInterval(zeit, 10000);
|
||||
|
||||
// Laeuft der Stream schon, steht unten, worum es geht. Das ist der
|
||||
// Unterschied zwischen einem Pausenbild und einem Standbild.
|
||||
var thema = document.getElementById('thema');
|
||||
function stand(s) {
|
||||
thema.textContent = s.live && s.category ? 'heute: ' + s.category : '';
|
||||
}
|
||||
Overlay.on('stream', stand);
|
||||
Overlay.on('state', function (ev) { stand(ev.stream); });
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user