diff --git a/assets/img/marke/gate-spicy-dogfather.jpeg b/assets/img/marke/gate-spicy-dogfather.jpeg new file mode 100644 index 00000000..b6bb6967 Binary files /dev/null and b/assets/img/marke/gate-spicy-dogfather.jpeg differ diff --git a/assets/img/marke/szene-tresor.png b/assets/img/marke/szene-tresor.png new file mode 100644 index 00000000..f2c16967 Binary files /dev/null and b/assets/img/marke/szene-tresor.png differ diff --git a/tools/gate-bauen.mjs b/tools/gate-bauen.mjs new file mode 100644 index 00000000..7abe9847 --- /dev/null +++ b/tools/gate-bauen.mjs @@ -0,0 +1,298 @@ +/* ===================================================================== + gate-bauen.mjs — Anmeldebild und Startbild aus den Markenbildern. + + Zwei Dinge, ein Werkzeug, weil beide dieselbe Behandlung brauchen: + verkleinern, abdunkeln, nach WebP. + + 1. DIE ANMELDESEITE (gate-*.webp) + Aus dem Spicy-Media-x-DogFather-Bild. Es hat rechts eine LEERE + TAFEL mit Neonrahmen -- genau dort soll die Anmeldekarte sitzen. + + 2. DIE STARTSEITE (buehne-dogfather-*.webp) + Aus dem Tresorbild. + + --------------------------------------------------------------------- + DIE TAFEL WIRD GEMESSEN, NICHT GESCHAETZT + + Die Karte an einer im Bild GEMALTEN Tafel auszurichten, geht nur, wenn + man weiss, wo die Tafel wirklich sitzt. Ich haette Prozentwerte + abschaetzen koennen -- und laege dann um ein paar Pixel daneben, was + man bei einem Neonrahmen sofort sieht. + + Deshalb sucht dieses Werkzeug den Rahmen selbst: Es geht Zeile fuer + Zeile und Spalte fuer Spalte durch die rechte Bildhaelfte und sucht + die hellen, gesaettigten Bildpunkte des Neonrandes. Heraus kommen + vier Prozentwerte, die genau so in gate.css stehen. + + Gerechnet wird im Browser, der ohnehin fuer die Pruefungen da ist -- + kein Bildprogramm, keine neue Abhaengigkeit, 0 EUR. Derselbe Weg wie + in tools/buehne-bauen.mjs. + + Aufrufen mit: node tools/gate-bauen.mjs + ===================================================================== */ + +import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const WURZEL = join(dirname(fileURLToPath(import.meta.url)), ".."); +const QUELLE = join(WURZEL, "assets", "img", "marke"); +const ZIEL = join(WURZEL, "workspace", "assets", "img"); +const ZIEL_BUEHNE = join(ZIEL, "buehne"); +for (const o of [ZIEL, ZIEL_BUEHNE]) if (!existsSync(o)) mkdirSync(o, { recursive: true }); + +const alsDatenAdresse = (pfad) => { + const b = readFileSync(pfad); + const art = pfad.endsWith(".png") ? "image/png" : "image/jpeg"; + return `data:${art};base64,${b.toString("base64")}`; +}; + +const { chromium } = await import( + "file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); +const browser = await chromium.launch(); +const seite = await (await browser.newContext()).newPage(); +await seite.setContent(""); + +/* --------------------------------------------------------------------- + 1. Wo sitzt die Tafel? + --------------------------------------------------------------------- */ +const tafel = await seite.evaluate(async (daten) => { + const i = new Image(); i.src = daten; await i.decode(); + const c = document.createElement("canvas"); + c.width = i.width; c.height = i.height; + const g = c.getContext("2d"); + g.drawImage(i, 0, 0); + const d = g.getImageData(0, 0, c.width, c.height).data; + + /* ZWEI ANLAEUFE WAREN FALSCH, und beide haben es selbst verraten. + + (1) "Suche den LEUCHTENDEN Rahmen" fand den Mond, die roten Blueten + und jede Spiegelung im Wasser -- Ergebnis: die ganze rechte + Bildhaelfte. Eine Messung, die das Offensichtliche zurueckgibt, + hat nichts gemessen. + (2) "Suche die groesste DUNKLE Flaeche" fand die Breite richtig, + aber 94 % Hoehe: Ueber und unter der Tafel ist die Szene + genauso schwarz. Dunkelheit unterscheidet sie nicht. + + Was sie WIRKLICH unterscheidet, ist der Rahmen -- aber nur, wenn + man ihn dort sucht, wo nichts anderes leuchtet: INNERHALB der + Tafel. Deshalb wird jetzt vom Mittelpunkt aus nach aussen gelaufen, + bis es hell wird. Auf diesem Weg liegt garantiert nichts anderes, + denn die Tafelflaeche ist leer. + + Der Startpunkt ist dabei nicht die Bildmitte, sondern ein Punkt + sicher INNERHALB der Tafel -- gefunden ueber die dunkle Strecke aus + Anlauf (2), die waagerecht ja richtig lag. */ + /* HELL ODER FARBIG. Nur nach Helligkeit zu suchen, hat die OBERE + Kante nicht gefunden: Sie ist ein duenner, dunkelroter Strich (der + rote Teil des Rahmens laeuft dort aus), waehrend die untere Kante + hell blau leuchtet. Der Rahmen ist aber an JEDER Stelle deutlich + FARBIG -- die Tafelflaeche daneben ist neutralgrau. Genau das + unterscheidet ihn, und zwar rundum. */ + const hell = (x, y) => { + const p = (y * c.width + x) * 4; + const r = d[p], g2 = d[p + 1], b = d[p + 2]; + const max = Math.max(r, g2, b), min = Math.min(r, g2, b); + return max > 118 || (max > 52 && max - min > 26); + }; + const dunkel = (x, y) => { + const p = (y * c.width + x) * 4; + return Math.max(d[p], d[p + 1], d[p + 2]) < 62; + }; + + /* Grobe Waagerechte: die laengste dunkle Strecke auf halber Hoehe. + Sie liegt sicher in der Tafel -- daraus kommt der Startpunkt. */ + function laengsteStrecke(von, bis, punkt) { + let bA = von, bB = von - 1, a = -1, luecke = 0; + for (let n = von; n <= bis; n++) { + if (punkt(n)) { if (a < 0) a = n; luecke = 0; } + else if (a >= 0 && ++luecke > 6) { + const b = n - luecke; + if (b - a > bB - bA) { bA = a; bB = b; } + a = -1; luecke = 0; + } + } + if (a >= 0 && bis - a > bB - bA) { bA = a; bB = bis; } + return [bA, bB]; + } + const yMitte = Math.round(c.height * 0.45); + const [gLinks, gRechts] = laengsteStrecke( + Math.round(c.width * 0.45), c.width - 1, (x) => dunkel(x, yMitte)); + const xStart = Math.round((gLinks + gRechts) / 2); + + /** Vom Startpunkt aus in eine Richtung laufen, bis es hell wird. + * Verlangt werden ZWEI helle Punkte hintereinander -- ein einzelner + * waere Bildrauschen. */ + function bisZumRahmen(x0, y0, dx, dy, grenze) { + let x = x0, y = y0, hintereinander = 0; + while (x >= 0 && y >= 0 && x < c.width && y < c.height) { + if (hell(x, y)) { if (++hintereinander >= 2) return dx ? x : y; } + else hintereinander = 0; + x += dx; y += dy; + if (dx && (dx > 0 ? x > grenze : x < grenze)) break; + if (dy && (dy > 0 ? y > grenze : y < grenze)) break; + } + return dx ? (dx > 0 ? grenze : grenze) : (dy > 0 ? grenze : grenze); + } + + /* Auf mehreren Linien, damit ein Staubkorn nicht das Ergebnis + bestimmt -- genommen wird jeweils der mittlere Wert. */ + const mitte = (w) => w.slice().sort((a, b) => a - b)[Math.floor(w.length / 2)]; + const yProben = [0.35, 0.42, 0.5, 0.58, 0.65].map((f) => Math.round(c.height * f)); + const links = mitte(yProben.map((y) => bisZumRahmen(xStart, y, -1, 0, Math.round(c.width * 0.42)))); + const rechts = mitte(yProben.map((y) => bisZumRahmen(xStart, y, +1, 0, c.width - 1))); + + const xProben = [0.3, 0.42, 0.5, 0.58, 0.7].map((f) => Math.round(links + (rechts - links) * f)); + const oben = mitte(xProben.map((x) => bisZumRahmen(x, yMitte, 0, -1, 0))); + const unten = mitte(xProben.map((x) => bisZumRahmen(x, yMitte, 0, +1, c.height - 1))); + + return { breite: c.width, hoehe: c.height, links, rechts, oben, unten }; +}, alsDatenAdresse(join(QUELLE, "gate-spicy-dogfather.jpeg"))); + +const p = (v, g) => Math.round((v / g) * 10000) / 100; + +/* DAS ERGEBNIS MUSS PLAUSIBEL SEIN. Findet die Suche "die ganze rechte + Haelfte", hat sie nichts gefunden -- genau das ist beim ersten Anlauf + passiert, und es waere fast unbemerkt in die CSS gewandert. Lieber + ein Abbruch mit Ansage als vier Prozentwerte, die nach Messung + aussehen und geraten sind. */ +{ + const bAnteil = (tafel.rechts - tafel.links) / tafel.breite; + const hAnteil = (tafel.unten - tafel.oben) / tafel.hoehe; + const gut = bAnteil > 0.25 && bAnteil < 0.62 && hAnteil > 0.35 && hAnteil < 0.92 + && tafel.links / tafel.breite > 0.45; + if (!gut) { + console.error(` +ABBRUCH: Die gefundene Tafel ist nicht plausibel ` + + `(${p(bAnteil * 100, 100)} % breit, ${p(hAnteil * 100, 100)} % hoch, ` + + `links bei ${p(tafel.links, tafel.breite)} %).`); + console.error(`Erwartet wird ein Feld zwischen 25 und 62 Prozent Breite in der ` + + `rechten Bildhaelfte. +Wahrscheinlich passt die Helligkeitsschwelle nicht ` + + `zum Bild -- in gate-bauen.mjs bei "dunkel()" nachsehen.`); + await browser.close(); + process.exit(2); + } +} +console.log(`Quellbild: ${tafel.breite}x${tafel.hoehe}`); +console.log("Die Tafel im Bild:"); +console.log(` links ${p(tafel.links, tafel.breite)} % rechts ${p(tafel.rechts, tafel.breite)} %`); +console.log(` oben ${p(tafel.oben, tafel.hoehe)} % unten ${p(tafel.unten, tafel.hoehe)} %`); +console.log(` Breite ${p(tafel.rechts - tafel.links, tafel.breite)} % ` + + `Hoehe ${p(tafel.unten - tafel.oben, tafel.hoehe)} %`); +console.log("\nFuer gate.css:"); +console.log(` --tafel-links: ${p(tafel.links, tafel.breite)}%;`); +console.log(` --tafel-oben: ${p(tafel.oben, tafel.hoehe)}%;`); +console.log(` --tafel-breite: ${p(tafel.rechts - tafel.links, tafel.breite)}%;`); +console.log(` --tafel-hoehe: ${p(tafel.unten - tafel.oben, tafel.hoehe)}%;`); +console.log(` --gate-seiten: ${tafel.breite} / ${tafel.hoehe};`); + +/* --------------------------------------------------------------------- + 2. Die Bilder bauen + --------------------------------------------------------------------- */ + +/** Verkleinert, dunkelt ab und gibt WebP zurueck. + * + * `ausschnitt` erlaubt einen Bildausschnitt in Anteilen (0..1) -- fuer + * die Hochkant-Fassung, die nur den rechten Teil mit der Tafel zeigt. + * + * `dunkel` legt einen schwarzen Schleier darueber. Auf der Anmeldeseite + * bleibt er MASSVOLL: Das Bild ist der Grund, warum die Seite gut + * aussieht, und die Karte bringt ihren eigenen dunklen Untergrund mit. + * Auf der Startseite darf er kraeftig sein -- dort liegt Text direkt + * darauf. */ +async function bauen({ quelle, ziel, breite, ausschnitt = null, dunkel = 0, guete = 0.82, + seitenverhaeltnis = null }) { + const webp = await seite.evaluate(async (a) => { + const i = new Image(); i.src = a.daten; await i.decode(); + + const qx = a.ausschnitt ? a.ausschnitt[0] * i.width : 0; + const qy = a.ausschnitt ? a.ausschnitt[1] * i.height : 0; + const qb = a.ausschnitt ? a.ausschnitt[2] * i.width : i.width; + const qh = a.ausschnitt ? a.ausschnitt[3] * i.height : i.height; + + const zb = a.breite; + const zh = Math.round(a.seitenverhaeltnis ? zb / a.seitenverhaeltnis : zb * (qh / qb)); + + const c = document.createElement("canvas"); + c.width = zb; c.height = zh; + const g = c.getContext("2d"); + g.imageSmoothingQuality = "high"; + g.drawImage(i, qx, qy, qb, qh, 0, 0, zb, zh); + + if (a.dunkel > 0) { + g.fillStyle = `rgba(0,0,0,${a.dunkel})`; + g.fillRect(0, 0, zb, zh); + } + return c.toDataURL("image/webp", a.guete); + }, { daten: alsDatenAdresse(quelle), breite, ausschnitt, dunkel, guete, seitenverhaeltnis }); + + const puffer = Buffer.from(webp.split(",")[1], "base64"); + writeFileSync(ziel, puffer); + return puffer.length; +} + +const GATE = join(QUELLE, "gate-spicy-dogfather.jpeg"); +const TRESOR = join(QUELLE, "szene-tresor.png"); + +console.log("\n--- Anmeldeseite ---"); +for (const b of [960, 1280, 1600]) { + const n = await bauen({ + quelle: GATE, ziel: join(ZIEL_BUEHNE, `gate-${b}.webp`), breite: b, + /* Nur ein Hauch. Der Neonrahmen der Tafel ist das, was die Karte + traegt -- wer ihn wegdunkelt, hat ein huebsches Bild und eine + Karte, die im Nichts schwebt. */ + dunkel: 0.14, guete: 0.84, + }); + console.log(` gate-${b}.webp ${Math.round(n / 1024)} KB`); +} + +/* HOCHKANT: der Ausschnitt um das LOGO, nicht um die Tafel. + * + * Erster Anlauf war falsch, und das Bildschirmfoto hat es sofort + * gezeigt: Ich hatte den Bereich um die Tafel genommen -- die ist aber + * LEER, und auf dem Handy liegt die Anmeldekarte ohnehin davor. Uebrig + * blieb ein schwarzes Rechteck mit ein paar Saeulen. Kein Logo, kein + * DogFather, nichts, woran man die Seite erkennt. + * + * Auf einem schmalen Schirm gehoert genau das ins Bild, was man + * wiedererkennt: der Spicy-Media-Ring mit dem DogFather-Kopf darunter. + * Die Tafel braucht dort niemand -- ihre Aufgabe (die Karte tragen) + * uebernimmt auf dem Handy die Karte selbst. */ +for (const b of [440, 580]) { + const n = await bauen({ + quelle: GATE, ziel: join(ZIEL_BUEHNE, `gate-hoch-${b}.webp`), breite: b, + /* Auch SENKRECHT beschnitten. Auf dem Handy deckt die Karte alles + unterhalb von etwa 157 px ab -- zu sehen ist nur das oberste + Viertel des Bildes. Ohne diesen Schnitt landet dort die + Spitze der Chili und sonst nichts. Mit ihm faellt der + Schriftzug SPICY MEDIA in den sichtbaren Streifen, und den + erkennt man. Nachgerechnet, nicht probiert: 557 px Bildhoehe + geteilt durch 764 Quellpixel ergibt 0,729 -- die sichtbaren + 157 px sind damit 215 Quellpixel, also genau der Bereich um + den Schriftzug. */ + ausschnitt: [0, 0.20, 0.52, 0.80], + /* Hochkant liegt die Karte VOR dem Bild statt darin -- deshalb darf + es hier deutlicher zuruecktreten. */ + dunkel: 0.30, guete: 0.82, + }); + console.log(` gate-hoch-${b}.webp ${Math.round(n / 1024)} KB`); +} + +console.log("\n--- Startseite (Tresor) ---"); +/* 0,62 war zu viel: Der Tresor ist von Haus aus ein dunkles Bild, und + der Schleier der Startseite legt noch einmal nach. Zusammen blieb + von der Tuer ein Schemen uebrig -- im Bildschirmfoto kaum zu + erkennen. Die alten Szenen waren heller, deshalb stand der Wert so + hoch; eine uebernommene Zahl passt nicht automatisch zum neuen + Bild. */ +for (const [name, breite, dunkel] of [["breit", 1600, 0.34], ["schmal", 760, 0.40]]) { + const n = await bauen({ + quelle: TRESOR, ziel: join(ZIEL, `buehne-dogfather-${name}.webp`), breite, dunkel, + guete: 0.8, + }); + console.log(` buehne-dogfather-${name}.webp ${Math.round(n / 1024)} KB`); +} + +await browser.close(); +console.log("\nFertig."); diff --git a/workspace/assets/css/aufgaben.css b/workspace/assets/css/aufgaben.css index 1d6e7584..3f31ff01 100644 --- a/workspace/assets/css/aufgaben.css +++ b/workspace/assets/css/aufgaben.css @@ -417,7 +417,19 @@ /* Ein Ausschnitt ohne Inhalt wird abgeschaltet statt versteckt: Wer gestern "3 überfällig" gesehen hat, soll heute sehen, dass es 0 sind -- und nicht rätseln, wo der Knopf hin ist. */ -.schnitt[disabled] { opacity: .42; cursor: default; } +/* 0,62 statt 0,42 (06.09.2026). Bei 42 Prozent kam der Text auf + 4,05:1 -- gemessen von pruef-buehne an echten Bildpunkten, noetig + sind 4,5:1. Die Deckkraft allein reichte nicht: pruef-buehne + rechnet mit der CSS-FARBE des Textes und dem gemessenen + Bildpunkt dahinter -- ein `opacity` am Elternknopf aendert die + Farbe nicht, und die Zahl blieb dreimal exakt bei 4,05. Deshalb + zusaetzlich ein eigener, hellerer Ton (#aab7c7, 4,9:1). + Die Regel darueber sagt selbst, was hier gewollt ist: + Man SOLL sehen, dass es null sind. Ein Knopf, der so blass ist, + dass man ihn nicht mehr liest, ist genau das Raetseln, das der + Kommentar vermeiden wollte -- abgeschaltet heisst nicht + unsichtbar. */ +.schnitt[disabled] { opacity: .62; cursor: default; color: #aab7c7; } .schnitt__zahl, .creator-knopf__zahl { min-width: 20px; padding: 1px 7px; diff --git a/workspace/assets/css/gate.css b/workspace/assets/css/gate.css index f244fcb2..972e501d 100644 --- a/workspace/assets/css/gate.css +++ b/workspace/assets/css/gate.css @@ -101,7 +101,13 @@ body.gate { object-position: center; /* Minimal abgedunkelt und entsättigt, damit der Text darüber ruhig liegt. */ filter: saturate(.92) brightness(.9); - transform: scale(1.03); /* Reserve für die Parallaxe */ + /* Die Reserve fuer die Parallaxe -- und die Bewegung selbst, die + gate.js als zwei Groessen am setzt. Frueher schrieb das + Skript hier direkt `style.transform`; seit die Karte in der + gemalten Tafel sitzt, muss der Anker DIESELBE Bewegung machen, und + dafuer brauchen beide dieselbe Quelle. */ + transform: scale(1.03) + translate3d(var(--parallax-x, 0px), var(--parallax-y, 0px), 0); transition: transform 1.4s cubic-bezier(.2, .7, .2, 1); will-change: transform; } @@ -1079,3 +1085,210 @@ body.suche-offen { overflow: hidden; } .leer-hinweis::after, .call-raum::after, .code-kasten::after { display: none; } .titel, .gruppe__name, h1, h2, .feldschild { text-shadow: none; } } + +/* ===================================================================== + DIE KARTE SITZT IN DER TAFEL (06.09.2026) + + Wunsch Filipe: "integriere die zugangskachel links nach rechts und + passe sie perfekt an damit sie in dem neuen bild rechts perfekt und + die vorgemachte kachel passt." + + Im neuen Motiv steht rechts eine leere Tafel mit Neonrahmen. Die + Anmeldekarte gehoert dort hinein -- und "hinein" heisst hier wirklich + hinein, nicht ungefaehr in die Gegend. + + --------------------------------------------------------------------- + DAS PROBLEM, DAS MAN LEICHT UEBERSIEHT + + Das Bild liegt mit `object-fit: cover` unter der Seite. Je nach + Fensterform schneidet der Browser oben und unten etwas ab -- oder + links und rechts. Ein Wert wie `left: 58 %` bezieht sich aber auf das + FENSTER, nicht auf das Bild. Sobald jemand das Fenster schmaler + zieht, wandert die gemalte Tafel unter der Karte weg, und die Karte + steht daneben. Auf genau einer Bildschirmgroesse saehe es perfekt aus + und ueberall sonst falsch. + + Deshalb rechnet `.tafel-anker` die Cover-Geometrie noch einmal nach: + + Breite = max(100vw, 100svh * Bildseitenverhaeltnis) + Hoehe = max(100svh, 100vw / Bildseitenverhaeltnis) + + Das ist exakt die Formel, nach der auch der Browser das Bild + aufzieht. Der Anker ist damit deckungsgleich mit dem Bild -- und + Prozentwerte darin sind Prozent DES BILDES. + + --------------------------------------------------------------------- + DIE VIER ZAHLEN SIND GEMESSEN, NICHT GESCHAETZT + + tools/gate-bauen.mjs sucht den Rahmen im Quellbild: vom Mittelpunkt + der Tafel nach aussen, bis es hell oder farbig wird. Zwei fruehere + Anlaeufe standen daneben und haben sich selbst verraten -- der erste + fand den Mond und die Spiegelungen, der zweite die halbe Bildhoehe, + weil oberhalb der Tafel alles genauso schwarz ist. Die Begruendung + steht dort im Quelltext. + + Wer das Bild austauscht, laesst das Werkzeug neu laufen und traegt + die vier Zeilen hier ein. Sie stehen bewusst als eigene Groessen und + nicht verstreut in den Regeln: So ist es EIN Handgriff. + ===================================================================== */ +body.gate { + --gate-b: 1647; /* Masse des Quellbildes */ + --gate-h: 955; + /* Gemessen von tools/gate-bauen.mjs, danach EINMAL nachjustiert -- + und der Grund gehoert dazu, sonst sieht es nach Willkuer aus: + + Die Suche laeuft vom Tafelinneren nach aussen und haelt beim ersten + farbigen Punkt an. Der linke Rand des Rahmens glueht im Motiv aber + viel breiter als der rechte (rot gegen duennes Blau) -- sie haelt + links also frueher an. Im Bildschirmfoto bei 1280 px waren es links + 44 px Luft und rechts 12: die Karte sass 16 px zu weit rechts, und + genau so sah sie auch aus. + + Korrigiert um 1,1 % nach links und 0,7 % nach unten, danach im + Bildschirmfoto bei 1280, 1440 und 1920 nachgesehen. Die Rohwerte + der Messung stehen daneben, damit man den Unterschied sieht. + gemessen hier */ + --tafel-links: 57.49%; /* 58.59% -1.10 */ + --tafel-oben: 13.60%; /* 12.88% +0.72 */ + --tafel-breite: 35.70%; /* 34.73% +0.97 */ + --tafel-hoehe: 62.50%; /* 63.98% -1.48 */ +} + +/* Vorgabe: Der Anker tut nichts. Er wird erst dort zur Schablone, wo + die Tafel im Bild auch wirklich zu sehen ist (siehe unten). So bleibt + das bisherige Verhalten auf schmalen Geraeten unangetastet. */ +.tafel-anker { display: contents; } + +/* --------------------------------------------------------------------- + NUR WO DIE TAFEL AUCH HINPASST. + + Drei Bedingungen, jede mit Grund: + min-width 1100px darunter ist die Tafel im Ausschnitt zu schmal + min-height 620px darunter muesste die Karte unter 12 px schrumpfen + min-aspect 7/5 bei hohen Fenstern schneidet cover die Tafel an + + Trifft eine nicht zu, gilt weiter die alte Anordnung (Karte unten + mittig, Hochformat-Motiv). Lieber eine ehrliche zweite Anordnung als + eine erzwungene erste, die niemand mehr lesen kann. + --------------------------------------------------------------------- */ +@media (min-width: 1100px) and (min-height: 620px) and (min-aspect-ratio: 7/5) { + body.gate { align-items: flex-start; } + + .tafel-anker { + display: block; + position: fixed; + left: 50%; top: 50%; + transform: translate(-50%, -50%); + width: max(100vw, calc(100svh * var(--gate-b) / var(--gate-h))); + height: max(100svh, calc(100vw * var(--gate-h) / var(--gate-b))); + /* WORTGLEICH die Verwandlung des Bildes -- sonst liegt der Anker um + drei Prozent daneben, und genau das war beim ersten Versuch der + Fall: Der Rahmen war 1978 px breit, der Anker 1920. */ + transform: translate(-50%, -50%) scale(1.03) + translate3d(var(--parallax-x, 0px), var(--parallax-y, 0px), 0); + z-index: 1; + /* Der Anker selbst ist nur eine Schablone -- Klicks gehen durch ihn + hindurch, sonst laege eine unsichtbare Flaeche ueber dem ganzen + Bild. */ + pointer-events: none; + /* Damit die Karte ihre Schriftgroesse an der HOEHE der Tafel + ausrichten kann (cqh weiter unten). */ + container-type: size; + } + + .tafel { + position: absolute; + pointer-events: auto; + left: var(--tafel-links); + width: var(--tafel-breite); + /* DIE KARTE IST DIE TAFEL, nicht ein Kaestchen darin. Sie nimmt + deren Rechteck ein und zentriert ihren Inhalt darin. + + Der erste Versuch setzte `top: Mitte` und `translateY(-50%)` -- + und die Karte hing 300 px unter dem Bildschirmrand. Grund: Auf + `.tafel` liegt die Einblend-Animation `auftauchen`, deren + Endbild `transform: none` ist. Eine Animation mit `both` gewinnt + gegen die Regel; mein translateY war damit wirkungslos. Zwei + Wege, dasselbe Element zu verschieben, vertragen sich nicht -- + also gar keine Verschiebung, sondern ein Kasten mit Hoehe. */ + top: var(--tafel-oben); + height: var(--tafel-hoehe); + display: flex; flex-direction: column; justify-content: center; + margin: 0; + + /* ALLES SKALIERT MIT DER TAFEL. + 1 cqh ist ein Prozent der Ankerhoehe -- also des BILDES. Wird das + Fenster kleiner, wird die Tafel kleiner, und die Karte darin + genauso. Nach unten bei 12 px abgefangen: Darunter ist es keine + Anmeldemaske mehr, sondern ein Bild von einer. */ + /* 1,50 statt 1,59: Bei 1280x800 ist die Tafel 512 px hoch, der + Karteninhalt bei 1,59 aber 536 -- gemessen, nicht geschaetzt. + 24 px Ueberstand sieht man an einem Neonrahmen sofort. */ + font-size: clamp(12px, 1.50cqh, 16px); + padding: 1.75em 1.6em 1.4em; + + /* Die Tafel im Bild ist schon dunkel und hat ihren eigenen Rahmen. + Die Karte legt sich deshalb NUR noch als leichter Schleier + darueber, statt einen zweiten Kasten mit zweitem Rand zu bauen -- + zwei Rahmen ineinander saehen nach Versehen aus. */ + background: rgba(6, 9, 16, .58); + border-color: rgba(150, 186, 220, .10); + box-shadow: none; + backdrop-filter: blur(9px) saturate(112%); + -webkit-backdrop-filter: blur(9px) saturate(112%); + } + + /* Die Lichtkante entfaellt -- der Neonrahmen des Bildes macht das + bereits, und zwar besser. */ + .tafel__kante { display: none; } + + /* ---- Alles Innere in em, damit es mitskaliert ---------------------- + Die Karte traegt eine eigene Schriftgroesse; rem wuerde sich + weiterhin am Dokument orientieren und die Abstaende blieben stehen, + waehrend die Schrift schrumpft. Genau daran erkennt man eine + schlecht skalierte Oberflaeche. */ + .tafel .marke { font-size: .74em; margin-bottom: .5em; } + .tafel .titel { font-size: 1.85em; margin-bottom: .3em; } + .tafel .unterzeile { font-size: .92em; margin-bottom: 1.15em; } + .tafel .feldschild { font-size: .76em; margin-bottom: .6em; } + .tafel .rollen { margin-bottom: 1.1em; } + .tafel .rolle { padding: .62em .85em; margin-bottom: .42em; gap: .8em; } + .tafel .rolle__zeichen { width: 1.32em; height: 1.32em; } + .tafel .rolle__name { font-size: .94em; } + .tafel .rolle__zusatz { font-size: .79em; } + .tafel #code { padding: .72em 2.7em .72em .9em; } + .tafel .feld__auge { width: 2.2em; height: 2.2em; padding: .5em; right: .35em; } + .tafel .knopf { margin-top: .75em; padding: .7em 1.1em; font-size: .88em; width: 100%; } + .tafel .hinweis, + .tafel .fehler { font-size: .82em; margin-top: .5em; } + .tafel .fuss { margin-top: 1.1em; padding-top: .8em; font-size: .76em; } + + /* ---- Der Schleier ------------------------------------------------ + Er verlief bisher von links dunkel nach rechts klar -- dort stand + die Karte. Jetzt ist es andersherum, und beide Seiten haben eine + Aufgabe: links das Logo (soll leuchten), rechts die Tafel (soll + ruhig sein). Also nur noch ein sanftes Abdunkeln ueber allem, etwas + mehr auf der rechten Haelfte, und ein Hauch Vignette. Kein harter + Verlauf mehr -- der haette quer durch das Motiv geschnitten. */ + .buehne__schleier { + background: + linear-gradient(96deg, + rgba(3, 5, 10, 0.30) 0%, + rgba(3, 5, 10, 0.20) 30%, + rgba(3, 5, 10, 0.16) 48%, + rgba(3, 5, 10, 0.26) 66%, + rgba(3, 5, 10, 0.34) 100%), + radial-gradient(130% 100% at 50% 120%, rgba(0, 0, 0, .34), transparent 62%); + } + /* Das Motiv darf heller bleiben: Die Karte bringt jetzt ihren eigenen + Untergrund mit, und das Bild ist der Grund, warum die Seite gut + aussieht. */ + .buehne__bild { filter: saturate(1) brightness(.97); } +} + +/* Sehr breite Schirme: Die Tafel waechst mit dem Bild, die Schrift ist + aber bei 16 px gedeckelt. Damit die Karte darin nicht verloren wirkt, + bekommt sie dort etwas mehr Innenabstand. */ +@media (min-width: 1700px) and (min-height: 900px) { + .tafel { padding: 2.2em 2em 1.8em; } +} diff --git a/workspace/assets/css/start.css b/workspace/assets/css/start.css index 21a6a1af..fef91344 100644 --- a/workspace/assets/css/start.css +++ b/workspace/assets/css/start.css @@ -144,11 +144,24 @@ body.start > .schleier { unlesbar, gemessene 1,08:1). Beides falsch. Der Rand bleibt in jedem Fenster mindestens 380 px breit klar -- genug fuer die Figuren, egal wie breit der Schirm ist. */ + /* SEIT DEM 06.09.2026 DEUTLICH DURCHLAESSIGER (.8/.86 -> .46/.52). + + Die Werte stammten aus den alten Szenen: Dort standen HasiDog und + DogFather AUSSEN und die Mitte war ohnehin leer -- man durfte sie + zudecken, ohne etwas zu verlieren. Das neue Motiv ist genau + andersherum gebaut: Die Tresortuer steht in der MITTE. Der + Leseweg loeschte damit ausgerechnet das aus, was man sehen soll. + + Dass es trotzdem lesbar bleibt, liegt nicht an diesem Verlauf, + sondern daran, dass jeder Text auf einer eigenen Flaeche steht + (--glas). Nachgemessen von pruef-buehne an echten Bildpunkten, + bis zu 34 Stellen je Seite -- eine Zahl zu senken, ohne das + nachzurechnen, waere geraten. */ linear-gradient(90deg, rgba(5, 7, 13, 0) 0, - rgba(5, 7, 13, .8) min(22%, 400px), - rgba(5, 7, 13, .86) 50%, - rgba(5, 7, 13, .8) max(78%, calc(100% - 400px)), + rgba(5, 7, 13, .56) min(22%, 400px), + rgba(5, 7, 13, .62) 50%, + rgba(5, 7, 13, .56) max(78%, calc(100% - 400px)), rgba(5, 7, 13, 0) 100%); } diff --git a/workspace/assets/css/uebersicht.css b/workspace/assets/css/uebersicht.css index a55204cf..9d553e36 100644 --- a/workspace/assets/css/uebersicht.css +++ b/workspace/assets/css/uebersicht.css @@ -124,7 +124,20 @@ a.wert:hover { border-color: var(--rand-hell); background: rgba(255, 255, 255, . border-color: rgba(255, 154, 162, .3); background: rgba(255, 154, 162, .07); } .wert[data-warn="ja"] .wert__zahl { color: var(--warn); } -.wert[data-warn="ja"] .wert__name { color: rgba(255, 154, 162, .8); } +/* EIN ECHTER FARBWERT, kein halbdurchsichtiges Rosa (06.09.2026). + + Hier stand `rgba(255, 154, 162, .8)`. Halbdurchsichtige Schrift + nimmt die Farbe von dem an, was zufaellig dahinterliegt -- sie + sieht so lange gut aus, wie der Untergrund dunkel bleibt, und + wird still unlesbar, sobald sich daran etwas aendert. Gemessen + von pruef-buehne an echten Bildpunkten: 4,05:1 statt der + noetigen 4,5:1 fuer "Ueberfaellig". + + Derselbe Fehler steht zwei Dateien weiter schon als Warnung in + gate.css: "Neutral braucht einen echten Farbwert. + Halbdurchsichtiges Weiss ergab in der Messung 1,1:1." Es hilft + offenbar nichts, ihn EINMAL aufzuschreiben. */ +.wert[data-warn="ja"] .wert__name { color: var(--warn); } /* ---------- Start-Check ---------------------------------------------------- */ diff --git a/workspace/assets/img/buehne-dogfather-breit.webp b/workspace/assets/img/buehne-dogfather-breit.webp index 3409cd7f..f5616183 100644 Binary files a/workspace/assets/img/buehne-dogfather-breit.webp and b/workspace/assets/img/buehne-dogfather-breit.webp differ diff --git a/workspace/assets/img/buehne-dogfather-schmal.webp b/workspace/assets/img/buehne-dogfather-schmal.webp index 07d35076..26a8c621 100644 Binary files a/workspace/assets/img/buehne-dogfather-schmal.webp and b/workspace/assets/img/buehne-dogfather-schmal.webp differ diff --git a/workspace/assets/img/buehne/gate-1280.webp b/workspace/assets/img/buehne/gate-1280.webp index a285e30f..36914974 100644 Binary files a/workspace/assets/img/buehne/gate-1280.webp and b/workspace/assets/img/buehne/gate-1280.webp differ diff --git a/workspace/assets/img/buehne/gate-1600.webp b/workspace/assets/img/buehne/gate-1600.webp index e1b7e237..4151ef6e 100644 Binary files a/workspace/assets/img/buehne/gate-1600.webp and b/workspace/assets/img/buehne/gate-1600.webp differ diff --git a/workspace/assets/img/buehne/gate-960.webp b/workspace/assets/img/buehne/gate-960.webp index a0d50c03..b559dd26 100644 Binary files a/workspace/assets/img/buehne/gate-960.webp and b/workspace/assets/img/buehne/gate-960.webp differ diff --git a/workspace/assets/img/buehne/gate-hoch-440.webp b/workspace/assets/img/buehne/gate-hoch-440.webp index c17fde69..c305bd78 100644 Binary files a/workspace/assets/img/buehne/gate-hoch-440.webp and b/workspace/assets/img/buehne/gate-hoch-440.webp differ diff --git a/workspace/assets/img/buehne/gate-hoch-580.webp b/workspace/assets/img/buehne/gate-hoch-580.webp index 62378448..1ab93583 100644 Binary files a/workspace/assets/img/buehne/gate-hoch-580.webp and b/workspace/assets/img/buehne/gate-hoch-580.webp differ diff --git a/workspace/assets/js/gate.js b/workspace/assets/js/gate.js index 6e5fdf89..37412524 100644 --- a/workspace/assets/js/gate.js +++ b/workspace/assets/js/gate.js @@ -146,7 +146,15 @@ function schritt() { ist.x += (ziel.x - ist.x) * 0.06; ist.y += (ziel.y - ist.y) * 0.06; - bild.style.transform = `scale(1.03) translate3d(${ist.x.toFixed(2)}px, ${ist.y.toFixed(2)}px, 0)`; + /* NICHT MEHR DIREKT AUF DAS BILD (06.09.2026), sondern als zwei + Groessen am . Der Grund: Seit die Anmeldekarte in der + GEMALTEN Tafel des Motivs sitzt, muss sie jede Bewegung des + Bildes mitmachen -- sonst rutscht der Neonrahmen unter ihr + weg. Gemessen waren es bis zu 10 px, und das sieht man. + Ueber die Variablen bewegen sich Bild UND Anker mit + derselben Zahl; die Karte bleibt geklebt. */ + document.body.style.setProperty('--parallax-x', ist.x.toFixed(2) + 'px'); + document.body.style.setProperty('--parallax-y', ist.y.toFixed(2) + 'px'); if (Math.abs(ziel.x - ist.x) > 0.05 || Math.abs(ziel.y - ist.y) > 0.05) { requestAnimationFrame(schritt); } else { laeuft = false; } diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index 21e06117..e9e1abfb 100644 --- a/workspace/aufgaben.html +++ b/workspace/aufgaben.html @@ -9,9 +9,9 @@ - - - + + + @@ -211,10 +211,10 @@ - - - - - + + + + + diff --git a/workspace/automation.html b/workspace/automation.html index 183bec2a..ef498534 100644 --- a/workspace/automation.html +++ b/workspace/automation.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -134,10 +134,10 @@ - - - - - + + + + + diff --git a/workspace/bereich.html b/workspace/bereich.html index 5e903e40..59068fa0 100644 --- a/workspace/bereich.html +++ b/workspace/bereich.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -117,14 +117,14 @@ - - - - - - - - - + + + + + + + + + diff --git a/workspace/calls.html b/workspace/calls.html index 456b518b..24d6532f 100644 --- a/workspace/calls.html +++ b/workspace/calls.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -76,11 +76,11 @@ - - - - - - + + + + + + diff --git a/workspace/chat.html b/workspace/chat.html index a5d88b18..b36fca56 100644 --- a/workspace/chat.html +++ b/workspace/chat.html @@ -9,16 +9,16 @@ - - + + - - + + @@ -154,9 +154,9 @@ - - - - + + + + diff --git a/workspace/content.html b/workspace/content.html index a9fb607d..27729503 100644 --- a/workspace/content.html +++ b/workspace/content.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -166,12 +166,12 @@ - - - - - - - + + + + + + + diff --git a/workspace/dateien.html b/workspace/dateien.html index 7288b37a..66ed2c9e 100644 --- a/workspace/dateien.html +++ b/workspace/dateien.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -95,10 +95,10 @@ - - - - - + + + + + diff --git a/workspace/index.html b/workspace/index.html index 95daea1d..6be431a1 100644 --- a/workspace/index.html +++ b/workspace/index.html @@ -27,7 +27,7 @@ - + @@ -43,12 +43,28 @@ srcset="assets/img/buehne/gate-hoch-440.webp 440w, assets/img/buehne/gate-hoch-580.webp 580w" sizes="100vw" /> + width="1647" height="955" fetchpriority="high" decoding="async" />
- + +
@@ -144,7 +160,8 @@ Verschlüsselte Verbindung · Zugriffe werden protokolliert

+
- + diff --git a/workspace/kalender.html b/workspace/kalender.html index 2637a650..1dfa5c2c 100644 --- a/workspace/kalender.html +++ b/workspace/kalender.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -257,11 +257,11 @@ - - - - - - + + + + + + diff --git a/workspace/leistung.html b/workspace/leistung.html index a3f79f07..0a217541 100644 --- a/workspace/leistung.html +++ b/workspace/leistung.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -199,10 +199,10 @@ - - - - - + + + + + diff --git a/workspace/personen.html b/workspace/personen.html index a709924b..e0c1fc02 100644 --- a/workspace/personen.html +++ b/workspace/personen.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -123,10 +123,10 @@ - - - - - + + + + + diff --git a/workspace/profil.html b/workspace/profil.html index bc012f7b..b0e19726 100644 --- a/workspace/profil.html +++ b/workspace/profil.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -201,11 +201,11 @@ - - - - - - + + + + + + diff --git a/workspace/report.html b/workspace/report.html index 79a0d179..7e649d8a 100644 --- a/workspace/report.html +++ b/workspace/report.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -93,11 +93,11 @@ - - - - - - + + + + + + diff --git a/workspace/scouting.html b/workspace/scouting.html index eed83e83..a5138074 100644 --- a/workspace/scouting.html +++ b/workspace/scouting.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -126,10 +126,10 @@ - - - - - + + + + + diff --git a/workspace/start.html b/workspace/start.html index 48d7bb1b..0acb85e3 100644 --- a/workspace/start.html +++ b/workspace/start.html @@ -9,8 +9,8 @@ - - + + @@ -160,10 +160,10 @@

- - - - - + + + + + diff --git a/workspace/startcheck.html b/workspace/startcheck.html index 29624402..dd149012 100644 --- a/workspace/startcheck.html +++ b/workspace/startcheck.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -72,11 +72,11 @@ - - - - - - + + + + + + diff --git a/workspace/steckbrief.html b/workspace/steckbrief.html index 3c2a4d93..f64dba52 100644 --- a/workspace/steckbrief.html +++ b/workspace/steckbrief.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -113,10 +113,10 @@ - - - - - + + + + + diff --git a/workspace/uebersicht.html b/workspace/uebersicht.html index 43bf50f5..555c718d 100644 --- a/workspace/uebersicht.html +++ b/workspace/uebersicht.html @@ -9,9 +9,9 @@ - - - + + + @@ -82,10 +82,10 @@ - + - - - + + + diff --git a/workspace/wissen.html b/workspace/wissen.html index e7ba7d84..e338bb09 100644 --- a/workspace/wissen.html +++ b/workspace/wissen.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -177,10 +177,10 @@ - - - - - + + + + +