/* ===================================================================== DIE WILLKOMMENSKACHEL BEKOMMT ALLE FARBEN DES HAUSES (22.09.2026) --------------------------------------------------------------------- Filipe: „die farbe dieser kachel soll alle farben haben die es auf der ganze seite gibt, also nicht gemischt sondern ich will alle farben übelst geil in der kachel haben die kachel soll alle farben haben die wir benutzt haben. soll komplett farbig sein und ultra geil aussehen lass dir was einfallen und überrasch mich bitte." „NICHT GEMISCHT" IST DIE EIGENTLICHE ANSAGE. Ein Verlauf über alle Töne wäre ein Brei: Zwischen zwei Nachbarfarben entstehen Zwischentöne, die es im Haus gar nicht gibt, und die einzelne Farbe ist nicht mehr zu erkennen. Jede Farbe bleibt deshalb für sich — man kann die Kachel ablaufen und jede Kachel der Wand darin wiederfinden. ZWEITER ANLAUF AM 22.09.2026: PUNKTE STATT STREIFEN. Der erste Entwurf gab jeder Farbe einen eigenen Streifen. Filipe: „Das mit den Farben ist mega. Aber irgendwie passen die balken/streifen nicht … schöner wirken, wenn die Farben als bunte Punkte auf der gesamten Kachel wären." Er hat recht, und der Grund steht in start.css: Jede Kachel des Hauses ist ein Sternenfeld. Die Streifen waren die einzige Fläche weit und breit mit harten Kanten — richtig in der Farbe, falsch in der Form. Jetzt sind es Punkte in derselben Bauart wie überall, nur bunt statt weiß. WARUM DAS EIN WERKZEUG IST UND KEINE HANDARBEIT Die Liste der Farben steht nicht hier, sie wird ABGELEITET — aus denselben Kachellisten, die die Startseite baut (`bereicheFuer` / `zusatzBereicheFuer` über alle Rollen und beide Häuser), und die Farbwerte dazu aus `start.css`. Eine abgeschriebene Liste wäre die, die beim nächsten neuen Bereich vergessen wird — und dann hätte die Kachel „alle Farben" eben doch nicht alle. Kommt morgen eine Kachel dazu, genügt ein Lauf: node tools/kachel-regenbogen.mjs nur zeigen node tools/kachel-regenbogen.mjs --schreiben eintragen Und `pruef-willkommen` zählt nach, ob der Block noch zu den tatsächlich benutzten Tönen passt — sonst wäre „einmal gelaufen" dasselbe wie „von Hand gepflegt", nur unsichtbar. AUGENSCHONEND BLEIBT PFLICHT Die Töne werden nicht roh nebeneinandergelegt. Jeder wird mit dem dunklen Hausgrund gemischt, genau wie auf jeder anderen Kachel auch (dort: `color-mix(--ton 62%, --flaeche)`). Die Kachel ist dadurch voller Farbe und trotzdem so dunkel wie ihre Nachbarn — und der Lesesaum unten, der auf allen Kacheln liegt, hält den Text lesbar. Keine Bewegung, kein Flackern. ===================================================================== */ import { readFileSync, writeFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; const HIER = dirname(fileURLToPath(import.meta.url)); const CSS = join(HIER, "..", "workspace", "assets", "css", "start.css"); const ANFANG = "/* ---- ANFANG GERECHNET: kachel-regenbogen.mjs ---- */"; const ENDE = "/* ---- ENDE GERECHNET ---- */"; /* ---- Welche Töne werden wirklich benutzt? --------------------------- */ /* Dasselbe Verfahren wie in kachel-farben-entzerren.mjs. Es steht hier noch einmal und nicht in einer gemeinsamen Datei, weil beide Werkzeuge sonst voneinander abhängen — und das eine soll laufen können, auch wenn am anderen gerade gebaut wird. Was zählt, ist dass BEIDE ableiten statt abzuschreiben. */ async function benutzteToene() { const w = await import("../server/workspace.js"); const rollen = ["admin", "hand", "linke", "modi", "gast", "manager", "scout", "creator", "spicy"]; const toene = new Map(); /* ton -> Set(Kachelnamen) */ for (const r of rollen) { for (const haus of ["crew", "agentur"]) { const p = { id: 1, rolle: r, name: "X", haus }; let k = []; try { k = [...(w.bereicheFuer(p, haus) || []), ...(w.zusatzBereicheFuer(p, haus) || [])]; } catch { continue; } for (const kachel of k) { if (!kachel || kachel.ton == null) continue; if (!toene.has(kachel.ton)) toene.set(kachel.ton, new Set()); toene.get(kachel.ton).add(kachel.name); } } } return toene; } /* ---- Die Farbwerte dazu --------------------------------------------- */ function farbenAusCss(css) { const m = new Map(); for (const t of css.matchAll(/\[data-ton="(\d+)"\]\s*\{\s*--ton:\s*(#[0-9a-fA-F]{6})/g)) { m.set(Number(t[1]), t[2].toLowerCase()); } return m; } /* ---- Farbton für die Sortierung ------------------------------------- */ /* Sortiert wird nach Farbton, nicht nach Tonnummer. Die Nummern sind die Reihenfolge, in der Bereiche entstanden sind — nebeneinander ergäbe das ein Durcheinander. Nach Farbton sortiert läuft die Kachel einmal durch den Regenbogen, und genau das soll man sehen. */ function farbwinkel(hex) { const [r, g, b] = [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255) .map((c) => (c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4)); const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b); const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b); const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b); const A = 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s; const B = 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s; return (Math.atan2(B, A) * 180) / Math.PI; } /* ===================================================================== */ const css = readFileSync(CSS, "utf8"); const farben = farbenAusCss(css); const benutzt = await benutzteToene(); const fehlend = [...benutzt.keys()].filter((t) => !farben.has(t)); if (fehlend.length) { console.error("\nKONNTE NICHT NACHSEHEN: Für " + fehlend.length + " benutzte" + " Töne steht keine Farbe in start.css.\n Betroffen: " + fehlend.join(", ") + "\n Ohne sie wäre die Kachel unvollständig, und niemand sähe es."); process.exit(2); } if (benutzt.size < 8) { console.error("\nKONNTE NICHT NACHSEHEN: nur " + benutzt.size + " benutzte" + " Töne gefunden.\n Das sind zu wenige, um glaubhaft zu sein — vermutlich" + " hat sich der Aufbau\n der Kachellisten geändert und die Ableitung" + " greift ins Leere."); process.exit(2); } const liste = [...benutzt.keys()] .map((t) => ({ ton: t, farbe: farben.get(t), wer: [...benutzt.get(t)].join(", ") })) .sort((a, b) => farbwinkel(a.farbe) - farbwinkel(b.farbe)); console.log(liste.length + " benutzte Töne, nach Farbwinkel sortiert:\n"); for (const e of liste) { console.log(" Ton " + String(e.ton).padStart(2) + " " + e.farbe + " " + String(Math.round(farbwinkel(e.farbe))).padStart(4) + " Grad " + e.wer); } /* ---- Das Punktfeld bauen -------------------------------------------- UMGEBAUT AM 22.09.2026, von Streifen auf Punkte. Filipe, nach dem ersten Bildschirmfoto: „Ja ist cool mit den ganzen farben, aber ich finde das passt nicht so gut zum eigentlichen Stil. Würde glaube ich schöner wirken, wenn die Farben als bunte Punkte auf der gesamten Kachel wären, vielleicht auch eng an eng und größere und kleinere Punkte, damit es vom Stil her die Punkte der anderen Kacheln aufgreift, aber trotzdem extrem heraussticht. Das mit den Farben ist mega. Aber irgendwie passen die balken/streifen nicht." ER HAT DEN STIL DES HAUSES GENAUER GELESEN ALS ICH. Jede Kachel der Startseite ist ein STERNENFELD: nahe Sterne mit Hof, mittlere Sterne, ferner Staub, dazu ein schräger Schleier. Nachzulesen weiter oben in start.css unter „NAHE STERNE". Die Streifen waren ein Fremdkörper -- sie waren die einzige Fläche im Haus mit harten Kanten. Das Punktfeld ist dieselbe Bauart wie überall, nur trägt hier JEDER Punkt eine der Hausfarben statt Weiß. Damit greift die Kachel den Stil auf und sticht trotzdem heraus: Sie ist die einzige, deren Sterne bunt sind. DREI GRÖSSEN, DREI EIGENE RASTER. Jede Ebene enthält ALLE Farben -- sonst wäre „alle Farben" davon abhängig, welche Größe man gerade ansieht. Die drei Rasterweiten sind teilerfremd zueinander gewählt; dadurch überlagern sie sich nie regelmäßig und das Feld sieht gestreut aus statt gerastert. NICHTS DAVON IST GERATEN: Zahl der Punkte, Rasterweite und Reihung folgen aus der Anzahl der Farben. Kommt eine Kachel dazu, rückt alles von selbst zusammen. --------------------------------------------------------------------- */ /* Ein Sprung durch die Farbliste, der teilerfremd zu ihrer Länge ist. Damit liegen Nachbarpunkte nie auf benachbarten Farbwinkeln -- sonst entstünden Flecken aus fast gleichen Tönen, und die Kachel sähe wieder nach Verlauf aus. Abgeleitet, nicht eingetragen: eine feste Zahl wie 7 versagt still, sobald die Anzahl ein Vielfaches davon ist. */ function sprungFuer(n, teiler) { const ggt = (a, b) => (b ? ggt(b, a % b) : a); for (let s = Math.max(2, Math.round(n / teiler)); s < n; s++) { if (ggt(s, n) === 1) return s; } return 1; } /* Ein Zufall, der immer derselbe ist. `Math.random` wäre hier falsch: Zwei Läufe ergäben zwei verschiedene Blöcke, und jeder Vergleich („hat sich etwas geändert?") wäre wertlos. */ function streu(i, salz) { const x = Math.sin((i + 1) * 12.9898 + salz * 78.233) * 43758.5453; return x - Math.floor(x); } /* Eine Ebene: alle Farben einmal, auf ein verwackeltes Raster verteilt. */ function ebene({ zelle, radius, hof, deckung, salz, teiler }) { const n = liste.length; const sprung = sprungFuer(n, teiler); const spalten = Math.ceil(Math.sqrt(n)); const reihen = Math.ceil(n / spalten); const bx = zelle / spalten; const by = zelle / reihen; /* Rand frei halten: Ein Punkt, der über die Kachelkante ragt, wird beim Wiederholen abgeschnitten -- und die abgeschnittene Hälfte taucht auf der anderen Seite NICHT auf. Das sieht man sofort. */ const luft = radius + 1; const punkte = []; for (let i = 0; i < n; i++) { const e = liste[(i * sprung) % n]; const sp = i % spalten; const re = Math.floor(i / spalten); const mx = sp * bx + bx / 2; const my = re * by + by / 2; const jx = (streu(i, salz) - 0.5) * bx * 0.62; const jy = (streu(i, salz + 5) - 0.5) * by * 0.62; const x = Math.min(zelle - luft, Math.max(luft, mx + jx)); const y = Math.min(zelle - luft, Math.max(luft, my + jy)); /* Etwas Größenstreuung innerhalb der Ebene -- sonst sind es drei Sorten gleicher Kreise statt eines gewachsenen Feldes. */ const r = (radius * (0.78 + 0.44 * streu(i, salz + 11))).toFixed(2); const f = `color-mix(in srgb, ${e.farbe} ${deckung}%, #ffffff)`; const kern = `${f}, ${f} 34%`; const aussen = hof ? `, color-mix(in srgb, ${e.farbe} 55%, transparent) 52%` : ""; punkte.push(` radial-gradient(${r}px ${r}px at ${x.toFixed(1)}px ${y.toFixed(1)}px,` + ` ${kern}${aussen}, transparent 100%)`); } return { punkte, zelle }; } /* Drei Rasterweiten, absichtlich ohne gemeinsamen Teiler. Lägen sie z. B. bei 200/100/50, fielen alle drei Ebenen alle 200 px wieder aufeinander -- und das Auge sieht ein Muster, wo Streuung sein soll. */ const EBENEN = [ { zelle: 197, radius: 5.4, hof: true, deckung: 88, salz: 1, teiler: 3 }, { zelle: 131, radius: 3.0, hof: false, deckung: 82, salz: 2, teiler: 4 }, { zelle: 83, radius: 1.7, hof: false, deckung: 74, salz: 3, teiler: 5 }, ]; const gebaut = EBENEN.map(ebene); const punktGroessen = gebaut .map((g) => g.punkte.map(() => `${g.zelle}px ${g.zelle}px`).join(", ")) .join(", "); const anzahlPunkte = gebaut.reduce((s2, g) => s2 + g.punkte.length, 0); const block = `${ANFANG} /* ERZEUGT VON tools/kachel-regenbogen.mjs — NICHT VON HAND ÄNDERN. ${anzahlPunkte} Punkte in drei Größen, jede Größe einmal alle ${liste.length} benutzten Kacheltöne. Die Liste ist aus den Kachellisten abgeleitet; wer eine Kachel hinzufügt, lässt das Werkzeug erneut laufen. pruef-kachelfarben zählt nach, ob beides noch zusammenpasst. */ .kachel[data-regenbogen="ja"] { background-image: /* 1. DER LESESAUM — er liegt ZUERST, also obenauf, und ist der Grund, warum diese Kachel bunt sein darf, ohne dass die Schrift leidet. ZURUECKGENOMMEN AM 22.09.2026, und zwar auf eine Messung hin: Mit dem Saum der Streifenfassung kam der Text hier auf 8,27:1 — bei einer Grenze von 4,5. Das war kein sicherer Saum, das war ein Deckel: Er hat die untere Haelfte der Kachel verschluckt, und genau dort sollten Punkte sein („auf der gesamten kachel"). Jetzt kuerzer und schwaecher, der Rest der Luft bleibt als Sicherheit stehen. pruef-kachelfarben misst es nach jedem Lauf neu. */ linear-gradient(to top, rgba(4, 6, 14, .80) 0%, rgba(4, 6, 14, .48) 24%, rgba(4, 6, 14, .14) 44%, transparent 60%), /* 2. DIE VIGNETTE — außen dunkler als innen. Ohne sie wäre die Kachel eine Fläche, mit ihr ein Ausschnitt. */ radial-gradient(120% 120% at 50% 40%, transparent 46%, rgba(3, 5, 12, .34) 100%), /* 3. DIE GROSSEN PUNKTE, jeder mit Hof — das ist der Unterschied zwischen einem Licht und einem gestanzten Kreis. Dieselbe Bauart wie die „nahen Sterne" auf jeder anderen Kachel. */ ${gebaut[0].punkte.join(",\n")}, /* 4. DIE MITTLEREN — die Masse des Feldes. */ ${gebaut[1].punkte.join(",\n")}, /* 5. DER FEINE STAUB. Er füllt die Lücken: Ein Feld aus nur großen Punkten sieht aus wie aufgeklebt. */ ${gebaut[2].punkte.join(",\n")}, /* 6. EIN SCHRÄGER SCHLEIER quer über das Feld. Er bindet die ${anzahlPunkte} Punkte zu einer Fläche zusammen — ohne ihn schweben sie einzeln. Sehr blass; er soll Richtung geben, nicht auffallen. */ linear-gradient(104deg, rgba(255, 255, 255, .085) 0%, rgba(255, 255, 255, .02) 36%, rgba(0, 0, 0, .10) 70%, rgba(0, 0, 0, .24) 100%), /* 7. DER GRUND. Deckend, nicht durchscheinend: Die Kachel soll die Farben des Hauses ZEIGEN, nicht durch sie hindurch das Bühnenbild. */ linear-gradient(180deg, #0b1020 0%, #070b14 100%); background-size: 100% 100%, 100% 100%, ${punktGroessen}, 100% 100%, 100% 100%; background-repeat: no-repeat, no-repeat, ${gebaut.flatMap((g) => g.punkte.map(() => "repeat")).join(", ")}, no-repeat, no-repeat; /* Die Kante trägt den Kachelton mit — sonst wäre ausgerechnet die farbigste Kachel der Wand einfarbig umrandet. */ border-color: color-mix(in srgb, var(--ton) 38%, transparent); } /* EIN RUHIGER GRUND HINTER DER SCHRIFT — und nur dort. Beim ersten Blick aufs Handybild sass ein heller Punkt mitten unter dem Wort „gibt". Die Messung sagte trotzdem 8,2:1 und hatte recht: Sie mittelt ueber die ganze Textflaeche. Ein einzelner Punkt hinter einem duennen Buchstaben verschwindet in diesem Mittel — im Auge nicht. ZWEI ANLAEUFE, DIE BEIDE FALSCH WAREN: * Den Lesesaum wieder dunkler machen — dann ist die halbe Kachel zu, und gewollt waren Punkte auf der GESAMTEN Kachel. * Ein dunkler Teich im Kachelhintergrund, 400 px breit. Auf dem grossen Bildschirm sass er richtig; auf dem Handy ist die Kachel selbst nur 366 px breit, und der Teich hat fast alle Farben geschluckt. Eine Zahl in Pixeln, die auf einem Geraet passt, ist auf dem naechsten falsch. RICHTIG IST, DEN GRUND AN DEN TEXT ZU HAENGEN statt an die Kachel: Dann ist er immer genau so breit wie das, was er lesbar machen soll — auf jedem Geraet, ohne eine einzige Schwelle. Gemessen wird nicht, sondern abgeleitet. "z-index: -1" legt ihn hinter die Schrift, aber vor die Punkte. (Ohne Schraegstriche geschrieben: Dieser Kommentar steht INNERHALB einer Vorlagenzeichenkette, und ein Gegenstrich darin beendet sie.) */ .kachel[data-regenbogen="ja"] .kachel__text { position: relative; } .kachel[data-regenbogen="ja"] .kachel__text::before { content: ""; position: absolute; inset: -16px -34px -18px -22px; z-index: -1; /* KEIN RAND UND KEIN ECKRADIUS. Der erste Entwurf hatte beides, und dann stand eine Karte in der Karte -- man sah den Kasten und nicht den Text. Der Verlauf laeuft jetzt deutlich vor der Kante aus; dadurch ist er ein Schatten und keine Flaeche. */ /* MITTIG UND KLEINER ALS DER KASTEN. Der zweite Entwurf sass bei 28 % und war 112 % breit -- am linken Rand war er damit noch fast deckend, und das Rechteck schnitt ihn hart ab. Man sah die Kante. Ein Verlauf, der an keiner Seite die Kante erreicht, hat auch keine. */ background: radial-gradient(74% 90% at 50% 50%, rgba(4, 6, 14, .86) 0%, rgba(4, 6, 14, .60) 42%, rgba(4, 6, 14, .18) 68%, transparent 88%); } ${ENDE}`; if (!process.argv.includes("--schreiben")) { console.log("\n--- so sähe der Block aus ---\n"); console.log(block); console.log("\nNur gezeigt. Mit --schreiben wird es eingetragen."); process.exit(0); } let neu; if (css.includes(ANFANG) && css.includes(ENDE)) { const a = css.indexOf(ANFANG); const e = css.indexOf(ENDE) + ENDE.length; neu = css.slice(0, a) + block + css.slice(e); } else { neu = css.replace(/\s*$/, "\n\n") + block + "\n"; } writeFileSync(CSS, neu, "utf8"); console.log("\nEingetragen in workspace/assets/css/start.css (" + anzahlPunkte + " Punkte in drei Groessen, je " + liste.length + " Farben).");