/* Spiegel im Blaugrau-Eingang — Anzeige-Logik. * * Die Seite liegt statisch auf mirror.bbox.at und holt sich alles aus zwei * kleinen Dateien, die ein Cronjob auf derselben Box schreibt: * data/mirror.json Bitcoin, Wetter Wien, "Heute vor X Jahren" * data/nachricht.txt Team-Nachricht (leer = keine) * Dazu data/geburtstage.json, das mit dem Deploy mitkommt. * * Bewusst KEINE direkten API-Aufrufe mehr aus dem Browser: haengt eine Quelle * oder drosselt sie, bleibt am Spiegel der letzte gute Stand stehen. * * Emma Stark, 28.07.2026 */ var wochentageDE = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag']; var monateDE = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember']; var geburtstage = []; // [{tag, monat, name}] var historie = []; // [{jahre, jahr, text}] var historieIndex = 0; var nachrichtAktiv = false; var feiertage = {}; // {"2026-12-25": {name, icon}} $(document).ready(function () { uhrStellen(); setInterval(uhrStellen, 10000); ladeGeburtstage(); ladeDaten(); setInterval(ladeDaten, 300000); // Cron schreibt alle 5 Minuten ladeNachricht(); setInterval(ladeNachricht, 60000); // Nachricht soll schnell ankommen // Historie langsam durchwechseln — der Spiegel wird mehrmals taeglich // im Vorbeigehen gelesen, ein einziger Fakt pro Stunde waere fad. setInterval(naechsteHistorie, 45000); // Nach einem Deploy von selbst die neue Fassung holen — siehe pruefeVersion(). merkeVersion(); setInterval(pruefeVersion, 120000); // Sicherheitsnetz gegen Speicherlecks im Dauerbetrieb. Selten, weil ein // Reload den Bildschirm kurz aufblitzen laesst; Inhalte und Tageswechsel // aktualisieren sich ohnehin ohne Neuladen. setTimeout(function () { location.reload(); }, 21600000); // 6 Stunden }); /* ---------- Uhrzeit + Datum ---------- */ var heutigerTag = null; function uhrStellen() { var d = new Date(); // Um Mitternacht rechnen sich die drei Tage und die Geburtstage neu — // sonst stuende bis zum naechsten Reload der Vortag da. var datumsKennung = d.toDateString(); if (heutigerTag !== null && heutigerTag !== datumsKennung) { zeigeTage(); pruefeHeutigenGeburtstag(); } heutigerTag = datumsKennung; // Ohne Jahreszahl — kurz gehalten, damit die Zeile nicht ausufert. $('#tag').html(wochentageDE[d.getDay()] + ', ' + d.getDate() + '. ' + monateDE[d.getMonth()]); $('#uhrzeit').html(('0' + d.getHours()).slice(-2) + ':' + ('0' + d.getMinutes()).slice(-2)); $('#today').show(); } /* ---------- Geburtstage ---------- */ function ladeGeburtstage() { $.getJSON('data/geburtstage.json', function (d) { geburtstage = d || []; zeigeTage(); pruefeHeutigenGeburtstag(); }); } // Wer hat an diesem Datum Geburtstag? Liefert die Namen als Array. function geburtstagsNamen(datum) { var tag = datum.getDate(), monat = datum.getMonth() + 1; var treffer = []; for (var i = 0; i < geburtstage.length; i++) { if (geburtstage[i].tag === tag && geburtstage[i].monat === monat) { treffer.push(geburtstage[i].name); } } return treffer; } // Die naechsten drei Tage unten: Wochentag, darunter ggf. Torte + Name. function zeigeTage() { for (var i = 1; i <= 3; i++) { var d = new Date(); d.setDate(d.getDate() + i); $('.day' + i + ' h1').html(wochentageDE[d.getDay()]); var namen = geburtstagsNamen(d); var html = ''; for (var n = 0; n < namen.length; n++) { html += "
" + namen[n] + "
"; } $('.day' + i + ' .eintrag').html(html); var f = feiertage[datumsSchluessel(d)]; var zeile = $('.day' + i + ' .feiertag'); if (f) { zeile.attr('class', 'feiertag f-' + f.icon).html(f.name).show(); } else { zeile.hide().empty(); } } $('#kalender').show(); streifenAusmessen(); } // "2026-12-25" — lokal gerechnet, nicht ueber toISOString (das waere UTC und // wuerde abends auf den Folgetag springen). function datumsSchluessel(d) { return d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2); } // Der Mittelstreifen bekommt genau den Platz, der zwischen Bitcoin-Block und // Tagesvorschau uebrig ist. Noetig, weil die Tagesspalten je nach Inhalt // unterschiedlich hoch sind (Vorhersage + evtl. Feiertag + evtl. Geburtstag) — // mit fester Position waere der Streifen sonst irgendwann ueberlappt. function streifenAusmessen() { var untenFrei = $('#kalender').outerHeight() + 30; var obenFrei = $('#btc').position().top + $('#btc').outerHeight() + 30; $('#mitte').css({ top: obenFrei + 'px', bottom: untenFrei + 'px' }); } // Am Tag selbst: Vollbild, das sich periodisch fuer 15 Sekunden einblendet. // Dauerhaft waere es zu viel — man sieht sonst nie mehr Uhrzeit oder Kurs. // // Erst aufraeumen, dann neu setzen: die Funktion laeuft auch beim Tageswechsel // um Mitternacht. Ohne das Loeschen liefe der Timer von gestern weiter (das // Vollbild kaeme dann doppelt so oft) und der Name von gestern stuende bis zum // naechsten Reload weiter oben links. var geburtstagsTimer = null; function pruefeHeutigenGeburtstag() { if (geburtstagsTimer !== null) { clearInterval(geburtstagsTimer); geburtstagsTimer = null; } $('#todayevents').empty(); $('#geburtstag').hide(); var namen = geburtstagsNamen(new Date()); if (!namen.length) return; $('#todayevents').html("
" + namen.join(', ') + "
"); $('#geburtstag .gb-name').html(namen.join(' & ')); zeigeGeburtstagsVollbild(); geburtstagsTimer = setInterval(zeigeGeburtstagsVollbild, 120000); // alle 2 Minuten } function zeigeGeburtstagsVollbild() { $('#geburtstag').fadeIn(600); setTimeout(function () { $('#geburtstag').fadeOut(600); }, 15000); } /* ---------- Bitcoin + Historie (aus data/mirror.json) ---------- */ function ladeDaten() { $.getJSON('data/mirror.json?t=' + new Date().getTime(), function (d) { if (!d) return; if (d.btc) zeigeBitcoin(d.btc); if (d.wetter) zeigeWetter(d.wetter); if (d.feiertage) { feiertage = d.feiertage; zeigeTage(); } if (d.heute_vor && d.heute_vor.length) { historie = d.heute_vor; if (historieIndex >= historie.length) historieIndex = 0; zeigeHistorie(); } }); } function zeigeBitcoin(btc) { var rauf = btc.aenderung >= 0; $('.bitcoin .bcvalue').html(zahl(btc.eur) + ' €'); $('.bitcoin .usdvalue').html(zahl(btc.usd) + ' $'); $('.bitcoin .bcchange').html((rauf ? '▲' : '▼') + ' ' + Math.abs(btc.aenderung).toFixed(1).replace('.', ',') + ' %'); // up/down am HTML-Container — von dort faerben sich Prozentwert UND Graph. // NICHT am : jQuery 1.10 kann addClass auf SVG-Elementen nicht. $('.bitcoin').removeClass('up down').addClass(rauf ? 'up' : 'down'); zeichneKurve(btc.verlauf || []); } // Wetter Wien: aktuelles Icon + Temperatur, dazu die Vorhersage der 3 Tage unten. // Welches Icon (Tag- oder Nachtvariante) gilt, hat schon der Cron entschieden. function zeigeWetter(w) { $('.wetter-icon').attr('class', 'wetter-icon wi ' + w.icon); $('.wetter-temp').html(w.temperatur + '°'); $('#wetter').show(); // Vorhersage unten bei den drei Tagen — Icon + Tageshoechstwert. var v = w.vorhersage || []; for (var i = 0; i < v.length && i < 3; i++) { var tag = $('.day' + (i + 1)); tag.find('.vorschau-icon').attr('class', 'vorschau-icon wi ' + v[i].icon); tag.find('.vorschau-temp').html(v[i].max + '°'); } } // Baut aus der Kursreihe den SVG-Pfad fuer den 24h-Graphen (nur die Linie). function zeichneKurve(werte) { if (werte.length < 2) return; var B = 460, H = 60, rand = 4; // Rand, damit die Linie nicht abgeschnitten wird var min = Math.min.apply(null, werte); var max = Math.max.apply(null, werte); var spanne = (max - min) || 1; // flache Linie -> keine Division durch 0 var punkte = []; for (var i = 0; i < werte.length; i++) { punkte.push([ i / (werte.length - 1) * B, H - rand - ((werte[i] - min) / spanne) * (H - rand * 2) ]); } var linie = ''; for (var p = 0; p < punkte.length; p++) { linie += (p === 0 ? 'M' : ' L') + punkte[p][0].toFixed(1) + ',' + punkte[p][1].toFixed(1); } $('.bcchart .line').attr('d', linie); } /* ---------- Mittelblock: Nachricht ODER Historie ---------- */ // Eine gesetzte Team-Nachricht hat Vorrang und verdraengt die Historie. function ladeNachricht() { $.ajax({ url: 'data/nachricht.txt?t=' + new Date().getTime(), dataType: 'text', success: function (text) { var t = $.trim(text || ''); nachrichtAktiv = t.length > 0; if (nachrichtAktiv) { $('#message').html(t).show(); $('#history').hide(); } else { $('#message').hide().empty(); zeigeHistorie(); } }, error: function () { nachrichtAktiv = false; zeigeHistorie(); } }); } function zeigeHistorie() { if (nachrichtAktiv || !historie.length) return; var e = historie[historieIndex % historie.length]; $('#history .hist-label').html('Heute vor ' + e.jahre + ' Jahren'); $('#history .hist-text').html(e.text); $('#history').show(); } function naechsteHistorie() { if (nachrichtAktiv || historie.length < 2) return; historieIndex = (historieIndex + 1) % historie.length; zeigeHistorie(); } /* ---------- Selbstaktualisierung nach einem Deploy ---------- */ // bbox/deploy.sh legt eine Pruefsumme des Seiteninhalts in data/version.txt. // Der Spiegel merkt sie sich beim Laden und schaut alle zwei Minuten nach. // Aendert sie sich, wurde deployt -> einmal neu laden. Sonst nie. // Das erspart das haendische Neustarten UND das staendige Aufblitzen, das ein // Reload im Minutentakt bedeuten wuerde. var geladeneVersion = null; function merkeVersion() { holeVersion(function (v) { geladeneVersion = v; }); } function pruefeVersion() { holeVersion(function (v) { if (geladeneVersion && v && v !== geladeneVersion) { location.reload(); } }); } function holeVersion(fertig) { $.ajax({ url: 'data/version.txt?t=' + new Date().getTime(), dataType: 'text', success: function (t) { fertig($.trim(t || '')); }, error: function () { fertig(null); } // Aussetzer ignorieren, nicht neu laden }); } /* ---------- Hilfsmittel ---------- */ // 55612 -> "55.612" function zahl(n) { return String(Math.round(n)).replace(/\B(?=(\d{3})+(?!\d))/g, '.'); }