diff --git a/core/patterns.py b/core/patterns.py
index 45812e6..cda3be3 100644
--- a/core/patterns.py
+++ b/core/patterns.py
@@ -321,12 +321,16 @@ def _detect(piv, cur, atr, last_idx):
# Verkleinert: 7,5 MB -> 379 KB fuer alle 15.
# ⚠ Doppeltop und Doppelboden teilen sich ein Bild (die Vorlage zeigt beide),
# ebenso Dreifach-Top/-Boden und Flagge bullisch/baerisch.
-# ⚠ `inv_cup` hat KEIN eigenes Bild — lieber keins als ein falsches.
+# ⚠ `inv_cup` hat seit dem 20.08. ein eigenes Bild (vorher bewusst keins).
BILD = {
"double_top": "double", "double_bottom": "double",
"triple_top": "triple", "triple_bottom": "triple",
"hs": "hs", "inv_hs": "inv_hs",
"cup": "cup",
+ # ⚠ Am 2026-08-20 nachgereicht (User: "scanne neue Bilder"). Bis dahin
+ # stand hier ausdruecklich KEIN Eintrag — lieber kein Bild als ein
+ # falsches; die normale Tasse zu zeigen waere die falsche Richtung gewesen.
+ "inv_cup": "inv_cup",
"flag": "flag", "pennant": "pennant",
"wedge_rise": "wedge_rise", "wedge_fall": "wedge_fall",
"tri_asc": "tri_asc", "tri_desc": "tri_desc", "tri_sym": "tri_sym",
diff --git a/tools/check_nfalle.py b/tools/check_nfalle.py
index bec3f91..0003949 100644
--- a/tools/check_nfalle.py
+++ b/tools/check_nfalle.py
@@ -478,7 +478,15 @@ def _stufe_ids():
js = re.sub(r"(?m)^\s*//.*$", "", js)
html_ids = re.findall(r'id="([^"]+)"', html)
doppelt = sorted({i for i in html_ids if html_ids.count(i) > 1})
- fehlt = sorted(set(re.findall(r'\$(?:streng)?\("([^"]+)"\)', js)) - set(html_ids))
+ # ⚠⚠ ABSICHTLICH ausgeblendete Elemente. Der JS-Block bleibt stehen —
+ # er ist null-sicher (`$streng` + Waechter) — damit ein Zurueckholen eine
+ # reine HTML-Aenderung ist. Ohne diese Liste meldete die Stufe drei
+ # Dauer-Treffer, und eine Pruefung mit Dauer-Treffern wird ignoriert (die
+ # Lehre vom Linter). ⚠ Wer hier etwas eintraegt, MUSS den Zugriff im JS
+ # auf `$streng` + Waechter umstellen — sonst bricht der GESAMTE Render.
+ AUSGEBLENDET = {"hl-note", "pat-note", "trail-note"} # User-Wunsch 2026-08-20
+ fehlt = sorted(set(re.findall(r'\$(?:streng)?\("([^"]+)"\)', js))
+ - set(html_ids) - AUSGEBLENDET)
for i in fehlt:
befunde.append(f"ID verwaist: $(\"{i}\") ohne Element in index.html")
print(f" FEHLER verwaiste ID: {i}")
diff --git a/tools/render_check.mjs b/tools/render_check.mjs
index 2655d52..4c78ccd 100644
--- a/tools/render_check.mjs
+++ b/tools/render_check.mjs
@@ -26,6 +26,14 @@ import vm from "node:vm";
const pfad = process.argv[2] || "tests/fixtures/snapshot.json";
const snap = JSON.parse(fs.readFileSync(pfad, "utf-8"));
const src = fs.readFileSync("web/app.js", "utf-8");
+// Die real vorhandenen Element-IDs aus der ausgelieferten HTML.
+const HTML_IDS = new Set(
+ [...fs.readFileSync("web/index.html", "utf-8").matchAll(/id="([^"]+)"/g)]
+ .map((m) => m[1]));
+if (HTML_IDS.size < 50) {
+ console.log(` ❌ nur ${HTML_IDS.size} IDs aus index.html gelesen — Probe greift nicht`);
+ process.exit(1);
+}
function el() {
const o = {
@@ -44,7 +52,15 @@ function el() {
const ctx = {
console,
document: {
- getElementById: () => el(), querySelector: () => el(),
+ // ⚠⚠ NUR IDs, die es in `index.html` wirklich gibt (Fix 2026-08-20).
+ // Vorher lieferte die Attrappe fuer JEDE ID ein Element — damit konnte
+ // `$streng()` in der Probe NIE `null` zurueckgeben, und genau der Fall,
+ // fuer den es `$streng` gibt, war ungeprueft. Real aufgefallen beim
+ // Ausblenden von `#hl-note`: der Hauptzweig griff ungeschuetzt auf
+ // `hlEl.classList` zu, die Probe blieb gruen, im Browser haette es den
+ // GESAMTEN Render abgebrochen.
+ getElementById: (id) => (HTML_IDS.has(id) ? el() : null),
+ querySelector: () => el(),
querySelectorAll: () => [], createElement: () => el(),
addEventListener() {}, body: el(), documentElement: el(),
activeElement: null, hidden: false, visibilityState: "visible",
diff --git a/web/app.js b/web/app.js
index aa8d01a..188a896 100644
--- a/web/app.js
+++ b/web/app.js
@@ -748,9 +748,19 @@ function renderKontext(snap) {
trN.textContent = trTxt || "";
trN.classList.toggle("hidden", !trTxt);
}
- const hlEl = $("hl-note");
+ // ⚠ `$streng`, nicht das Auffang-`$`: das Element ist am 2026-08-20 auf
+ // User-Wunsch aus der Meldungen-Karte ENTFERNT. Mit dem Auffang-`$` liefe
+ // der Block ins Leere und meldete bei jedem Laden eine Konsolen-Warnung —
+ // ein Dauer-Fehlalarm fuer einen gewollten Zustand.
+ const hlEl = $streng("hl-note");
const h = snap && snap.hl_ctx;
- if (h && h.premium != null) {
+ // ⚠⚠ `hlEl` MUSS mitgeprueft werden. Das Element ist am 2026-08-20 auf
+ // User-Wunsch entfernt worden; ohne diese Bedingung wirft `hlEl.classList`
+ // einen TypeError — und der bricht den GESAMTEN Render ab (Dashboard
+ // friert auf Altwerten ein). Die else-Zweige hatten den Waechter, der
+ // Hauptzweig nicht. Gefunden von der Render-Probe, nachdem deren eigener
+ // blinder Fleck behoben war (die Attrappe gab fuer JEDE ID ein Element).
+ if (hlEl && h && h.premium != null) {
const fr = (h.funding != null) ? (h.funding * 100).toFixed(4) + " %" : "—";
const oi = (h.open_interest != null)
? (h.open_interest / 1e6).toFixed(2) + " Mio" : "—";
@@ -761,8 +771,8 @@ function renderKontext(snap) {
+ "der Perp notiert über dem Index, Longs zahlen. ⚠ Gemessen über 5.095 "
+ "Funding-Stundenwerte (7 Monate) gegen den CFD: kein belastbarer "
+ "Prognosewert.";
- } else {
- hlEl.classList.add("hidden");
+ } else if (hlEl) {
+ hlEl.classList.add("hidden");
}
}
@@ -1013,6 +1023,49 @@ function renderCone(cn) {
// ⚠ Bilder mit `loading="lazy"`: die 15 Vorlagen liegen verkleinert bei ~25 KB
// (Originale 436–611 KB), aber über WireGuard soll nur geladen werden, was
// wirklich sichtbar ist.
+// ══ BILD-MODALBOX für die Chartmuster (User-Wunsch 2026-08-20) ═════════════
+// ⚠⚠ BEWUSST OHNE BIBLIOTHEK — Begründung im Markup (`#bildbox`). Kurz: ein
+// Lightbox-Paket wäre 15–40 KB plus ein zweites Bedienkonzept, das Dashboard
+// läuft nur über WireGuard (kein CDN erreichbar), und das Projekt hat aus
+// demselben Grund Tailwind abgelehnt und die vendorte Chart-Bibliothek
+// entfernt. Was hier fehlt, sind ~30 Zeilen.
+// ⚠ Delegierter Klick auf `document`: die Kacheln werden bei JEDEM Render neu
+// erzeugt (`innerHTML = …`). Ein Handler direkt am `` wäre nach dem
+// nächsten Snapshot weg — genau die Sorte Fehler, die nur sporadisch auffällt.
+function bildboxOeffnen(bild, text) {
+ const box = $streng("bildbox"), img = $streng("bildbox-img");
+ if (!box || !img) return;
+ // Grosse Fassung (~133 KB) erst JETZT laden, nicht mit dem Dashboard.
+ img.src = `muster/gross/${bild}.jpg`;
+ img.alt = text || "";
+ const t = $streng("bildbox-txt");
+ if (t) t.textContent = text || "";
+ box.classList.remove("hidden");
+}
+
+function bildboxSchliessen() {
+ const box = $streng("bildbox");
+ if (box) box.classList.add("hidden");
+}
+
+document.addEventListener("click", (e) => {
+ const t = e.target;
+ if (t && t.classList && t.classList.contains("pt-img") && t.dataset
+ && t.dataset.bild) {
+ bildboxOeffnen(t.dataset.bild, t.dataset.txt || t.alt || "");
+ return;
+ }
+ // Klick auf den Hintergrund ODER den Schliessen-Knopf schliesst.
+ const box = $streng("bildbox");
+ if (box && !box.classList.contains("hidden")
+ && (t === box || (t && t.id === "bildbox-zu"))) {
+ bildboxSchliessen();
+ }
+});
+document.addEventListener("keydown", (e) => {
+ if (e.key === "Escape") bildboxSchliessen();
+});
+
function renderPatternsTF(d) {
const el = $streng("pt-tfgrid");
if (!el) return;
@@ -1028,7 +1081,9 @@ function renderPatternsTF(d) {
const arr = m.dir === "bull" ? "▲" : m.dir === "bear" ? "▼" : "◆";
const best = m.status === "confirmed";
const bild = m.bild
- ? ``
+ ? ``
: `
`
@@ -2405,7 +2460,12 @@ async function setMgGlobal() {
// Ein zugeklapptes ist nur nicht sichtbar - `textContent` wird
// weiter gesetzt, und die naechste Oeffnung zeigt den aktuellen Stand.
{
- const KEY = "oil_m15_akk";
+ // ⚠⚠ SCHLUESSEL GEBUMPT (2026-08-20). Der Default ist jetzt
+ // "eingeklappt" — aber im `localStorage` steht bei jedem, der die Karte je
+ // aufgeklappt hatte, noch `true`, und das gewinnt gegen den neuen Default.
+ // Ein neuer Schluessel setzt genau EINMAL zurueck; danach merkt sich die
+ // Karte die eigene Wahl wie bisher.
+ const KEY = "oil_m15_akk2";
let stand = {};
try { stand = JSON.parse(localStorage.getItem(KEY) || "{}"); } catch (e) { stand = {}; }
document.querySelectorAll("details.m15-akk").forEach(el => {
@@ -2459,7 +2519,7 @@ async function closeSlot(slot, label, pnl) {
// und der Browser zeigte Altes. Statt das jedes Mal von Hand zu diagnostizieren,
// sagt die Seite es jetzt selbst.
// ⚠ Bewusst SICHTBAR statt nur Konsole: die Konsole sieht am Handy niemand.
-const _JS_V = "205";
+const _JS_V = "207";
{
const htmlV = document.body && document.body.dataset
? document.body.dataset.htmlV : null;
diff --git a/web/index.html b/web/index.html
index 4e2f0ed..b1aa13d 100644
--- a/web/index.html
+++ b/web/index.html
@@ -12,9 +12,9 @@
⚠ LOKAL eingebunden, kein CDN: die PWA laeuft ueber WireGuard
und muss offline funktionieren. -->
-
+
-
+