21 Kachelfarben neu gerechnet -- keine zwei aehneln sich mehr
Filipe, screen24/25: "viele kacheln haben noch fast die gleiche farben,
ähneln sich sehr und ich will dass du komplett eskalierst ... alle seine
eigenen farben und so dass sie sich nicht ähneln. sehr wichtig nicht
ähneln!!!!"
Er hatte recht, und es liess sich messen statt bereden:
kleinster Abstand zweier Toene 0,033 (Dateien gegen Creator-Profile)
Paare unter 0,05 (kaum trennbar) 21 von 210
Spanne der Helligkeit 0,002
DIE URSACHE WAR EINE GUTE ABSICHT. Die alte Palette lief gleichmaessig
um EINEN Farbring, mit bewusst konstanter Helligkeit und Farbstaerke --
"dadurch wirken alle gleich stark und keine draengt sich vor". Genau das
erzeugt den Fehler: Bleibt alles ausser dem Farbton gleich, ist der
Farbton der einzige Unterschied. 360 Grad auf 21 Kacheln sind 17 Grad,
und 17 Grad sieht man nicht.
Jetzt variieren Helligkeit UND Farbstaerke mit. Zwei Farben mit
aehnlichem Ton stehen trotzdem weit auseinander, weil die eine hell und
satt und die andere dunkel und ruhig ist -- der Abstand bekommt eine
zweite und dritte Dimension.
kleinster Abstand 0,097 (dreimal so gross)
Paare unter 0,05 0 von 210
Helligkeitsspanne 0,242
Gerechnet in OKLab, weil dort der Zahlenabstand dem entspricht, was das
Auge als Unterschied empfindet. Die Auswahl ist eine Suche, kein
Geschmack: erst gierig den jeweils entferntesten Ton nehmen, dann so
lange tauschen, wie der KLEINSTE Abstand dadurch waechst.
Drei Bedingungen halten dabei, und alle drei stehen im Werkzeug als
Abbruch, nicht nur im Bericht:
lesbar mindestens 4,5:1 gegen den Grund (schlechteste: 4,50)
augenschonend Farbstaerke gedeckelt bei 0,17 -- satt ja, Neon nein
trennbar gemessen ueber ALLE Paare, nicht nur ueber Nachbarn im
Raster: Auf der Uebersicht stehen dieselben Kacheln in
anderer Reihenfolge nebeneinander.
Das Werkzeug bricht ab, wenn eine neue Palette schlechter waere als der
alte Stand (0,0328) -- sonst waere ein schlechter Lauf von einem guten
nicht zu unterscheiden.
pruef-start-ansicht EXIT=0, 140 Pruefungen. Die Farben zusaetzlich am
laufenden Browser abgelesen, nicht nur aus der Datei.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
+220
-142
@@ -1,173 +1,251 @@
|
|||||||
/* ===================================================================
|
/* ===================================================================
|
||||||
Die 17 Kachelfarben der Startseite -- und wer welche bekommt.
|
Die 21 Kachelfarben -- gerechnet, nicht gewaehlt.
|
||||||
|
|
||||||
Filipe wollte, dass JEDE Kachel ihre eigene Farbe hat. Ein erster
|
ANLASS (08.09.2026). Filipe: "viele kacheln haben noch fast die
|
||||||
Versuch mit frei gewaehlten Farben ergab Paare mit einem Abstand von
|
gleiche farben, ähneln sich sehr und ich will dass du komplett
|
||||||
1,4 -- also praktisch dieselbe Farbe. Das sieht nach Zufall aus, nicht
|
eskalierst ... alle seine eigenen farben und so dass sie sich nicht
|
||||||
nach Absicht.
|
ähneln. sehr wichtig nicht ähneln!!!!"
|
||||||
|
|
||||||
Deshalb wird hier gerechnet statt gewaehlt:
|
Er hat recht, und es liess sich messen. Die alte Palette lief
|
||||||
|
gleichmaessig um den Farbkreis, mit ABSICHTLICH konstanter Helligkeit
|
||||||
|
und Farbstaerke -- "dadurch wirken alle gleich stark und keine
|
||||||
|
draengt sich vor". Der Gedanke war gut und hat genau den Fehler
|
||||||
|
erzeugt, den Filipe sieht:
|
||||||
|
|
||||||
1. 17 Farbtoene, gleichmaessig um den Farbkreis verteilt, in OKLCH
|
Kleinster Abstand zweier Toene: 0,033 (Dateien gegen Creator)
|
||||||
gerechnet (dort sind gleiche Abstaende auch fuer das Auge gleich).
|
Paare unter 0,05 (kaum trennbar): 21 von 210
|
||||||
Helligkeit und Farbstaerke bleiben konstant -- dadurch wirken alle
|
Spanne der Helligkeit: 0,002
|
||||||
gleich stark und keine draengt sich vor. Wo eine Farbstaerke den
|
|
||||||
darstellbaren Bereich verliesse (Gelb und Gruen tun das frueher),
|
|
||||||
wird sie so weit gesenkt, bis sie hineinpasst.
|
|
||||||
|
|
||||||
2. Die Zuordnung ist eine Suche, kein Geschmack. Verglichen wird vom
|
Bei 21 Farben auf EINEM Ring mit gleicher Helligkeit bleibt als
|
||||||
Auge nur, was NEBENEINANDER liegt -- 17 Farben muessen sich also
|
einziger Unterschied der Farbton. Nachbarn liegen dann zwangslaeufig
|
||||||
nicht alle voneinander unterscheiden, sondern nur von ihren
|
dicht beieinander -- 360 Grad geteilt durch 21 sind 17 Grad, und
|
||||||
Nachbarn im Raster. Gesucht wird die Anordnung, bei der der
|
17 Grad Farbton sieht man schlicht nicht.
|
||||||
kleinste Abstand zweier Nachbarn so gross wie moeglich ist.
|
|
||||||
|
|
||||||
3. Unter allen Anordnungen, die eine harte Untergrenze schaffen, wird
|
WAS SICH AENDERT: Helligkeit und Farbstaerke variieren jetzt mit.
|
||||||
die genommen, deren Farben am besten zur Sache passen: LIVE rot,
|
Zwei Farben mit aehnlichem Farbton stehen dadurch trotzdem weit
|
||||||
weil "on air" rot ist. Technik gelb wie ein Warnschild. Reports
|
auseinander, weil die eine hell und satt und die andere dunkel und
|
||||||
gruen wie ein Ergebnis. Personen golden wie ein Schluessel.
|
ruhig ist. Der Abstand bekommt eine zweite und dritte Dimension.
|
||||||
|
|
||||||
|
GERECHNET WIRD IN OKLAB. Dort entspricht der Zahlenabstand dem, was
|
||||||
|
das Auge als Unterschied empfindet -- in RGB tut er das nicht: #ff0000
|
||||||
|
und #00ff00 sind rechnerisch gleich weit auseinander wie #000080 und
|
||||||
|
#0000ff, sehen aber voellig anders getrennt aus.
|
||||||
|
|
||||||
|
DREI BEDINGUNGEN, die jede Farbe halten muss:
|
||||||
|
|
||||||
|
1. LESBAR. Mindestens 4,5:1 gegen den dunklen Grund (#0a121e).
|
||||||
|
Eine Kachelfarbe traegt auch Text und Zahlen.
|
||||||
|
2. AUGENSCHONEND (Hausregel). Farbstaerke gedeckelt -- satt ja,
|
||||||
|
Neon nein. Deshalb liegt die Obergrenze der Buntheit bei 0,17
|
||||||
|
und nicht am Rand des Darstellbaren.
|
||||||
|
3. TRENNBAR. Der kleinste Abstand ZWISCHEN ALLEN PAAREN wird so
|
||||||
|
gross wie moeglich gemacht, nicht nur der zwischen Nachbarn im
|
||||||
|
Raster. Filipe sieht die Kacheln auch untereinander, nicht nur
|
||||||
|
nebeneinander -- und auf der Uebersichtsseite stehen sie in
|
||||||
|
anderer Reihenfolge als auf der Startseite.
|
||||||
|
|
||||||
Aufrufen mit: node tools/kachel-farben.mjs
|
Aufrufen mit: node tools/kachel-farben.mjs
|
||||||
Die Ausgabe wird nach start.css uebertragen (Abschnitt "Kacheln").
|
Die Ausgabe wird nach start.css uebertragen (Abschnitt "Kacheln").
|
||||||
=================================================================== */
|
=================================================================== */
|
||||||
|
|
||||||
const L = 0.655, C_MAX = 0.148;
|
/* ---------- Farbenlehre ------------------------------------------- */
|
||||||
|
|
||||||
function oklchZuRgb(L, C, hGrad) {
|
const zuLinear = (c) => (c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));
|
||||||
const h = hGrad * Math.PI / 180;
|
const zuSrgb = (c) => (c <= 0.0031308 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055);
|
||||||
const a = C * Math.cos(h), b = C * Math.sin(h);
|
|
||||||
const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
|
function oklabAusRgb(r, g, b) {
|
||||||
const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
|
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
||||||
const s_ = L - 0.0894841775 * a - 1.2914855480 * 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);
|
||||||
|
return [
|
||||||
|
0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s,
|
||||||
|
1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s,
|
||||||
|
0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** OKLCH -> sRGB. Gibt null zurueck, wenn die Farbe ausserhalb des
|
||||||
|
* darstellbaren Bereichs liegt -- dann wird sie nicht heimlich
|
||||||
|
* beschnitten, sondern verworfen. Ein beschnittener Wert waere eine
|
||||||
|
* ANDERE Farbe als die gerechnete, und alle Abstaende darauf falsch. */
|
||||||
|
function ausOklch(L, C, Hgrad) {
|
||||||
|
const h = (Hgrad * Math.PI) / 180;
|
||||||
|
const a = C * Math.cos(h), bb = C * Math.sin(h);
|
||||||
|
const l_ = L + 0.3963377774 * a + 0.2158037573 * bb;
|
||||||
|
const m_ = L - 0.1055613458 * a - 0.0638541728 * bb;
|
||||||
|
const s_ = L - 0.0894841775 * a - 1.2914855480 * bb;
|
||||||
const l = l_ ** 3, m = m_ ** 3, s = s_ ** 3;
|
const l = l_ ** 3, m = m_ ** 3, s = s_ ** 3;
|
||||||
const r = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
|
const r = +4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
|
||||||
const g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
|
const g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
|
||||||
const bl = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s;
|
const b = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s;
|
||||||
const gam = (v) => (v <= 0.0031308 ? 12.92 * v : 1.055 * Math.pow(v, 1 / 2.4) - 0.055);
|
const kanaele = [r, g, b].map(zuSrgb);
|
||||||
return [gam(r), gam(g), gam(bl)];
|
if (kanaele.some((c) => c < -0.002 || c > 1.002)) return null;
|
||||||
|
const bytes = kanaele.map((c) => Math.round(Math.min(1, Math.max(0, c)) * 255));
|
||||||
|
return {
|
||||||
|
hex: "#" + bytes.map((n) => n.toString(16).padStart(2, "0")).join(""),
|
||||||
|
oklab: oklabAusRgb(...bytes.map((n) => zuLinear(n / 255))),
|
||||||
|
leucht: 0.2126 * zuLinear(bytes[0] / 255) + 0.7152 * zuLinear(bytes[1] / 255)
|
||||||
|
+ 0.0722 * zuLinear(bytes[2] / 255),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
const imRaum = (rgb) => rgb.every((v) => v >= -0.001 && v <= 1.001);
|
|
||||||
const hex = (rgb) => "#" + rgb.map((v) =>
|
|
||||||
Math.round(Math.min(1, Math.max(0, v)) * 255).toString(16).padStart(2, "0")).join("");
|
|
||||||
|
|
||||||
/* 01.09.2026: 18 statt 17. Der eigene Steckbrief hat eine eigene
|
const abstand = (x, y) => Math.hypot(
|
||||||
Kachel bekommen (vorher teilte er sich die Seite mit der
|
x.oklab[0] - y.oklab[0], x.oklab[1] - y.oklab[1], x.oklab[2] - y.oklab[2]);
|
||||||
Creator-Akte). Damit aendert sich der Abstand zwischen allen Toenen
|
|
||||||
-- deshalb wird hier neu gerechnet und nicht eine Farbe dazuerfunden.
|
|
||||||
|
|
||||||
06.09.2026: 21. Drei Kacheln sind an einem Tag dazugekommen -- Chat,
|
/* Der Grund, auf dem die Kacheln liegen. */
|
||||||
Zahlen und Agentur -- und jede hatte sich zunaechst einen Ton mit
|
const GRUND_LEUCHT = 0.2126 * zuLinear(0x0a / 255)
|
||||||
einer bestehenden geteilt. Das faellt am Rechner kaum auf und ist
|
+ 0.7152 * zuLinear(0x12 / 255)
|
||||||
trotzdem falsch: Filipe wollte ausdruecklich, dass JEDE Kachel ihre
|
+ 0.0722 * zuLinear(0x1e / 255);
|
||||||
eigene Farbe hat, und pruef-start-ansicht hat es gemessen (18 Farben
|
const kontrast = (f) => (f.leucht + 0.05) / (GRUND_LEUCHT + 0.05);
|
||||||
auf 21 Kacheln). Eine Farbe dazuzuerfinden waere der bequeme Weg und
|
|
||||||
der falsche -- sie laege dann irgendwo zwischen zweien und saehe nach
|
/* ---------- Die Kacheln, in fester Reihenfolge --------------------- */
|
||||||
Zufall aus. Also wird der ganze Kreis neu geteilt. */
|
|
||||||
const ANZAHL = 21;
|
|
||||||
const winkel = (i) => (i * 360 / ANZAHL + 250) % 360;
|
|
||||||
function farbe(i) {
|
|
||||||
let C = C_MAX;
|
|
||||||
while (C > 0.02 && !imRaum(oklchZuRgb(L, C, winkel(i)))) C -= 0.004;
|
|
||||||
return hex(oklchZuRgb(L, C, winkel(i)));
|
|
||||||
}
|
|
||||||
|
|
||||||
const KACHELN = [
|
const KACHELN = [
|
||||||
"Dashboard", "Aufgaben", "Kalender", "Calls", "Chat", "Dateien",
|
"Dashboard", "Zahlen", "LIVE", "Chat", "Schutz", "Community", "Reports",
|
||||||
"Zahlen", "Mein Steckbrief", "Creator-Profile", "Start-Check", "LIVE", "Content",
|
"Kalender", "Bibliothek", "Steckbrief", "Personen", "Content", "Aufgaben",
|
||||||
"Community", "Technik", "Schutz", "Agentur", "Reports",
|
"Technik", "Calls", "Start-Check", "Agentur", "Automationen", "Dateien",
|
||||||
"Personen", "Scout", "Bibliothek", "Automationen",
|
"Creator-Profile", "Scout",
|
||||||
];
|
];
|
||||||
const N = (n) => KACHELN.indexOf(n);
|
|
||||||
|
|
||||||
/* Nachbarschaft aus dem tatsaechlichen Aufbau der Seite: drei Spalten,
|
/* Wo eine Kachel inhaltlich hingehoert. Das ist die einzige Stelle mit
|
||||||
Dashboard ueber zwei. Nebeneinander UND untereinander. */
|
Geschmack, und sie ist bewusst schwach gewichtet: Der Farbton ist ein
|
||||||
const NACHBARN = [
|
WUNSCH, die Trennbarkeit eine BEDINGUNG. Wo beides nicht zusammengeht,
|
||||||
/* Taeglich: Dashboard ueber zwei Spalten, dann Aufgaben daneben;
|
gewinnt die Trennbarkeit -- eine Kachel, die man nicht auseinanderhaelt,
|
||||||
darunter Kalender, Calls, Chat; darunter Dateien. */
|
nuetzt auch in der "richtigen" Farbe nichts. */
|
||||||
["Dashboard", "Aufgaben"], ["Dashboard", "Kalender"], ["Dashboard", "Calls"],
|
const WUNSCHTON = {
|
||||||
["Aufgaben", "Chat"], ["Kalender", "Calls"], ["Calls", "Chat"],
|
"LIVE": 25, // rot -- "on air"
|
||||||
["Kalender", "Dateien"],
|
"Technik": 95, // gelb wie ein Warnschild
|
||||||
/* Rund um den Creator: elf Kacheln in drei Spalten. */
|
"Reports": 150, // gruen wie ein Ergebnis
|
||||||
["Zahlen", "Mein Steckbrief"], ["Mein Steckbrief", "Creator-Profile"],
|
"Personen": 70, // golden wie ein Schluessel
|
||||||
["Zahlen", "Start-Check"], ["Mein Steckbrief", "LIVE"], ["Creator-Profile", "Content"],
|
"Kalender": 320, // pink, hebt den Tagesplan heraus
|
||||||
["Start-Check", "LIVE"], ["LIVE", "Content"],
|
"Dashboard": 250, // blau, der Ausgangspunkt
|
||||||
["Start-Check", "Community"], ["LIVE", "Technik"], ["Content", "Schutz"],
|
|
||||||
["Community", "Technik"], ["Technik", "Schutz"],
|
|
||||||
["Community", "Agentur"], ["Technik", "Reports"], ["Agentur", "Reports"],
|
|
||||||
/* Team & System. */
|
|
||||||
["Personen", "Scout"], ["Scout", "Bibliothek"], ["Personen", "Automationen"],
|
|
||||||
].map(([a, b]) => [N(a), N(b)]);
|
|
||||||
|
|
||||||
const abstand = (a, b) => {
|
|
||||||
const d = Math.abs(winkel(a) - winkel(b)) % 360;
|
|
||||||
return Math.min(d, 360 - d);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const WUNSCH = {
|
/* ---------- Kandidaten ---------------------------------------------- */
|
||||||
LIVE: 17, Content: 38, Technik: 81, Reports: 144, Personen: 59,
|
|
||||||
Schutz: 250, Community: 335, Dashboard: 250, Kalender: 208,
|
const KANDIDATEN = [];
|
||||||
Bibliothek: 271, "Start-Check": 165,
|
for (let H = 0; H < 360; H += 3) {
|
||||||
/* Die drei Neuen (06.09.2026): Chat gruen wie ein gelesener Haken,
|
for (const L of [0.60, 0.66, 0.72, 0.78, 0.84]) {
|
||||||
Zahlen blau wie eine Messung, Agentur orange -- die Farbe, die
|
/* Die groesste Farbstaerke suchen, die bei diesem Ton und dieser
|
||||||
"von aussen" sagt, ohne zu warnen. */
|
Helligkeit noch darstellbar ist -- gedeckelt bei 0,17
|
||||||
Chat: 150, Zahlen: 265, Agentur: 40,
|
(augenschonend, siehe Kopf). Von oben herunter, damit die Farbe
|
||||||
|
so satt wie erlaubt wird und nicht blass. */
|
||||||
|
for (let C = 0.17; C >= 0.05; C -= 0.005) {
|
||||||
|
const f = ausOklch(L, C, H);
|
||||||
|
if (!f) continue;
|
||||||
|
if (kontrast(f) < 4.5) break; // zu dunkel fuer Text
|
||||||
|
KANDIDATEN.push({ ...f, L, C, H });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Auswahl: den kleinsten Abstand so gross wie moeglich ----
|
||||||
|
|
||||||
|
Erst gierig (nimm immer die Farbe, die am weitesten von allen schon
|
||||||
|
gewaehlten weg ist), dann verbessern: Jede gewaehlte Farbe wird
|
||||||
|
probeweise gegen jede andere getauscht; wird der kleinste Abstand
|
||||||
|
dadurch groesser, bleibt der Tausch. Das laeuft, bis keine
|
||||||
|
Verbesserung mehr kommt.
|
||||||
|
|
||||||
|
Warum nicht einfach 21 gleichmaessige Punkte: Weil der darstellbare
|
||||||
|
Bereich keine Kugel ist. Gelb vertraegt viel Helligkeit und wenig
|
||||||
|
Buntheit, Blau umgekehrt. Ein gleichmaessiges Gitter liegt deshalb
|
||||||
|
teils ausserhalb, teils verschenkt es Platz. */
|
||||||
|
|
||||||
|
const anzahl = KACHELN.length;
|
||||||
|
let gewaehlt = [KANDIDATEN.reduce((a, b) => (a.L * 100 + a.H < b.L * 100 + b.H ? a : b))];
|
||||||
|
while (gewaehlt.length < anzahl) {
|
||||||
|
let beste = null, besterWert = -1;
|
||||||
|
for (const k of KANDIDATEN) {
|
||||||
|
if (gewaehlt.includes(k)) continue;
|
||||||
|
const naechster = Math.min(...gewaehlt.map((g) => abstand(k, g)));
|
||||||
|
if (naechster > besterWert) { besterWert = naechster; beste = k; }
|
||||||
|
}
|
||||||
|
gewaehlt.push(beste);
|
||||||
|
}
|
||||||
|
|
||||||
|
const kleinster = (menge) => {
|
||||||
|
let m = Infinity;
|
||||||
|
for (let i = 0; i < menge.length; i++)
|
||||||
|
for (let j = i + 1; j < menge.length; j++) m = Math.min(m, abstand(menge[i], menge[j]));
|
||||||
|
return m;
|
||||||
};
|
};
|
||||||
function wunschNaehe(z) {
|
|
||||||
let punkte = 0;
|
|
||||||
for (const [name, wunsch] of Object.entries(WUNSCH)) {
|
|
||||||
const d = Math.abs(winkel(z[N(name)]) - wunsch) % 360;
|
|
||||||
punkte += 180 - Math.min(d, 360 - d);
|
|
||||||
}
|
|
||||||
return punkte;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Erst die Unterscheidbarkeit sichern, dann unter allem, was das
|
let verbessert = true, runden = 0;
|
||||||
schafft, die passendste Anordnung waehlen. */
|
while (verbessert && runden < 60) {
|
||||||
const MINDEST = 95;
|
verbessert = false; runden++;
|
||||||
function bewerte(z) {
|
for (let i = 0; i < gewaehlt.length; i++) {
|
||||||
let schlechtestes = 999;
|
const jetzt = kleinster(gewaehlt);
|
||||||
for (const [a, b] of NACHBARN) schlechtestes = Math.min(schlechtestes, abstand(z[a], z[b]));
|
for (const k of KANDIDATEN) {
|
||||||
if (schlechtestes < MINDEST) return schlechtestes; // noch nicht gut genug
|
if (gewaehlt.includes(k)) continue;
|
||||||
return 1e6 + schlechtestes * 1000 + wunschNaehe(z);
|
const probe = gewaehlt.slice(); probe[i] = k;
|
||||||
}
|
if (kleinster(probe) > jetzt + 1e-9) { gewaehlt = probe; verbessert = true; break; }
|
||||||
|
|
||||||
/* Fester Zufall: Dieselbe Eingabe muss dieselbe Ausgabe ergeben, sonst
|
|
||||||
waeren die Farben nach jedem Lauf andere. */
|
|
||||||
let saat = 987654321;
|
|
||||||
const zufall = () => (saat = (saat * 1103515245 + 12345) % 2147483648) / 2147483648;
|
|
||||||
|
|
||||||
let beste = null, besteNote = -1;
|
|
||||||
for (let lauf = 0; lauf < 900; lauf++) {
|
|
||||||
const z = [...Array(ANZAHL).keys()];
|
|
||||||
/* ANZAHL - 1, nicht 16. Hier stand eine feste 16 -- bei achtzehn
|
|
||||||
Kacheln wurden die letzten beiden also nie mitgemischt, und der
|
|
||||||
Suchraum war stillschweigend kleiner als gedacht. Faellt nicht auf,
|
|
||||||
weil trotzdem eine Loesung herauskommt; nur eben nicht unbedingt
|
|
||||||
die beste. */
|
|
||||||
for (let i = ANZAHL - 1; i > 0; i--) { const j = Math.floor(zufall() * (i + 1)); [z[i], z[j]] = [z[j], z[i]]; }
|
|
||||||
let note = bewerte(z), besser = true;
|
|
||||||
while (besser) {
|
|
||||||
besser = false;
|
|
||||||
for (let a = 0; a < ANZAHL; a++) for (let b = a + 1; b < ANZAHL; b++) {
|
|
||||||
[z[a], z[b]] = [z[b], z[a]];
|
|
||||||
const neu = bewerte(z);
|
|
||||||
if (neu > note) { note = neu; besser = true; } else { [z[a], z[b]] = [z[b], z[a]]; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (note > besteNote) { besteNote = note; beste = [...z]; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let schlechtestes = 999, paar = "";
|
/* ---------- Zuordnung: Wunschton, wo er ohne Verlust geht ---------- */
|
||||||
for (const [a, b] of NACHBARN) {
|
|
||||||
const d = abstand(beste[a], beste[b]);
|
|
||||||
if (d < schlechtestes) { schlechtestes = d; paar = `${KACHELN[a]} / ${KACHELN[b]}`; }
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Kleinster Abstand zweier Nachbarn: ${schlechtestes.toFixed(1)}° (${paar})\n`);
|
const frei = gewaehlt.slice();
|
||||||
console.log("--- fuer start.css ---");
|
const zuteilung = new Map();
|
||||||
KACHELN.forEach((k, i) => {
|
/* Erst die Kacheln mit Wunsch -- jede bekommt die freie Farbe, deren
|
||||||
console.log(`.kachel[data-ton="${beste[i] + 1}"] { --ton: ${farbe(beste[i])}; } /* ${k}, ${Math.round(winkel(beste[i]))}° */`);
|
Ton ihrem Wunsch am naechsten liegt. */
|
||||||
|
for (const [name, wunsch] of Object.entries(WUNSCHTON)) {
|
||||||
|
if (!KACHELN.includes(name)) continue;
|
||||||
|
let beste = 0, bestAbw = Infinity;
|
||||||
|
frei.forEach((f, i) => {
|
||||||
|
const abw = Math.min(Math.abs(f.H - wunsch), 360 - Math.abs(f.H - wunsch));
|
||||||
|
if (abw < bestAbw) { bestAbw = abw; beste = i; }
|
||||||
|
});
|
||||||
|
zuteilung.set(name, frei.splice(beste, 1)[0]);
|
||||||
|
}
|
||||||
|
/* Der Rest der Reihe nach nach Farbton, damit benachbarte Kacheln im
|
||||||
|
Raster nicht zufaellig springen -- der Abstand ist ohnehin gesichert. */
|
||||||
|
frei.sort((a, b) => a.H - b.H);
|
||||||
|
for (const name of KACHELN) if (!zuteilung.has(name)) zuteilung.set(name, frei.shift());
|
||||||
|
|
||||||
|
/* ---------- Nachweis ------------------------------------------------ */
|
||||||
|
|
||||||
|
const ergebnis = KACHELN.map((n) => ({ name: n, ...zuteilung.get(n) }));
|
||||||
|
const min = kleinster(ergebnis);
|
||||||
|
const Ls = ergebnis.map((f) => f.oklab[0]);
|
||||||
|
let unter5 = 0, paare = 0;
|
||||||
|
for (let i = 0; i < ergebnis.length; i++)
|
||||||
|
for (let j = i + 1; j < ergebnis.length; j++) {
|
||||||
|
paare++; if (abstand(ergebnis[i], ergebnis[j]) < 0.05) unter5++;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("ALT (gemessen): kleinster Abstand 0,0328 · 21 von 210 Paaren unter 0,05"
|
||||||
|
+ " · Helligkeitsspanne 0,002\n");
|
||||||
|
console.log(`NEU: kleinster Abstand ${min.toFixed(4)} · ${unter5} von ${paare}`
|
||||||
|
+ ` Paaren unter 0,05 · Helligkeitsspanne ${(Math.max(...Ls) - Math.min(...Ls)).toFixed(3)}\n`);
|
||||||
|
|
||||||
|
const schlechtester = Math.min(...ergebnis.map(kontrast));
|
||||||
|
console.log(`Schlechtester Kontrast gegen den Grund: ${schlechtester.toFixed(2)}:1`
|
||||||
|
+ ` (Grenze 4,5) -> ${schlechtester >= 4.5 ? "haelt" : "HAELT NICHT"}\n`);
|
||||||
|
|
||||||
|
console.log("/* Die Nummern bleiben an ihren Kacheln -- eine Farbe gehoert zu");
|
||||||
|
console.log(" einem Bereich, nicht zu einem Platz im Raster. */");
|
||||||
|
ergebnis.forEach((f, i) => {
|
||||||
|
const n = `[data-ton="${i + 1}"]`.padEnd(16);
|
||||||
|
console.log(`${n}{ --ton: ${f.hex}; } /* ${f.name} */`);
|
||||||
});
|
});
|
||||||
console.log("\n--- fuer start.js (ton je Kachel) ---");
|
|
||||||
console.log(JSON.stringify(Object.fromEntries(KACHELN.map((k, i) => [k, beste[i] + 1]))));
|
/* DIE ZAHLEN GEHOEREN IN DIE BEDINGUNG, nicht nur in den Bericht:
|
||||||
console.log("\n--- alle Farben in der Reihenfolge der Plaetze ---");
|
Ein Lauf, der eine schlechtere Palette ausgibt als die alte, waere
|
||||||
console.log([...Array(ANZAHL).keys()].map((i) => farbe(i)).join(","));
|
sonst nicht von einem guten zu unterscheiden. */
|
||||||
|
if (min <= 0.0328) {
|
||||||
|
console.error(`\nFEHLER: Der kleinste Abstand (${min.toFixed(4)}) ist nicht besser als`
|
||||||
|
+ ` der alte (0,0328). Nichts uebernehmen.`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
if (schlechtester < 4.5) {
|
||||||
|
console.error(`\nFEHLER: Eine Farbe liegt unter 4,5:1 gegen den Grund.`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
if (ergebnis.length !== 21) {
|
||||||
|
console.error(`\nFEHLER: ${ergebnis.length} Farben statt 21.`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log(`\n${ergebnis.length} Farben, alle Bedingungen erfuellt.`);
|
||||||
|
|||||||
@@ -817,27 +817,50 @@ body.start > main { position: relative; z-index: 1; }
|
|||||||
data-ton: Dieselbe Nummer färbt die Kachel und die Hinweiszeile, die
|
data-ton: Dieselbe Nummer färbt die Kachel und die Hinweiszeile, die
|
||||||
dorthin führt. Eine Farbe gehört hier zu einem Bereich, nicht zu einem
|
dorthin führt. Eine Farbe gehört hier zu einem Bereich, nicht zu einem
|
||||||
Bauteil. */
|
Bauteil. */
|
||||||
[data-ton="1"] { --ton: #3e95e7; } /* Dashboard */
|
/* NEU GERECHNET AM 08.09.2026 (tools/kachel-farben.mjs).
|
||||||
[data-ton="2"] { --ton: #688beb; } /* Zahlen */
|
|
||||||
[data-ton="3"] { --ton: #8881e7; } /* LIVE */
|
Filipe: "viele kacheln haben noch fast die gleiche farben, ähneln sich
|
||||||
[data-ton="4"] { --ton: #a178dc; } /* Chat */
|
sehr ... alle seine eigenen farben und so dass sie sich nicht ähneln.
|
||||||
[data-ton="5"] { --ton: #b670ca; } /* Schutz */
|
sehr wichtig nicht ähneln!!!!" -- und er hatte recht, es liess sich
|
||||||
[data-ton="6"] { --ton: #c76ab3; } /* Community */
|
messen. Die alte Palette lief um EINEN Farbring mit absichtlich
|
||||||
[data-ton="7"] { --ton: #d36699; } /* Reports */
|
konstanter Helligkeit; damit blieb als einziger Unterschied der
|
||||||
[data-ton="8"] { --ton: #da657c; } /* Kalender */
|
Farbton, und 360 Grad auf 21 Kacheln sind 17 Grad. So wenig sieht man
|
||||||
[data-ton="9"] { --ton: #dc685d; } /* Bibliothek */
|
nicht.
|
||||||
[data-ton="10"] { --ton: #d96e3c; } /* Mein Steckbrief */
|
|
||||||
[data-ton="11"] { --ton: #d0780e; } /* Personen */
|
alt kleinster Abstand 0,033 · 21 von 210 Paaren unter 0,05
|
||||||
[data-ton="12"] { --ton: #bd8501; } /* Content */
|
· Helligkeitsspanne 0,002
|
||||||
[data-ton="13"] { --ton: #a98f0d; } /* Aufgaben */
|
neu kleinster Abstand 0,097 · 0 von 210 Paaren unter 0,05
|
||||||
[data-ton="14"] { --ton: #929909; } /* Technik */
|
· Helligkeitsspanne 0,242
|
||||||
[data-ton="15"] { --ton: #72a234; } /* Calls */
|
|
||||||
[data-ton="16"] { --ton: #47a858; } /* Start-Check */
|
Jetzt variieren Helligkeit UND Farbstaerke mit. Zwei Farben mit
|
||||||
[data-ton="17"] { --ton: #11aa7a; } /* Agentur */
|
aehnlichem Ton stehen dadurch trotzdem weit auseinander, weil die eine
|
||||||
[data-ton="18"] { --ton: #0da795; } /* Automationen */
|
hell und satt und die andere dunkel und ruhig ist.
|
||||||
[data-ton="19"] { --ton: #16a4a9; } /* Dateien */
|
|
||||||
[data-ton="20"] { --ton: #01a2bd; } /* Creator-Profile */
|
Gemessen in OKLab (dort entspricht der Zahlenabstand dem empfundenen
|
||||||
[data-ton="21"] { --ton: #119dd4; } /* Scout */
|
Unterschied), und jede Farbe haelt 4,5:1 gegen den dunklen Grund --
|
||||||
|
sie traegt auch Text. Wer sie neu rechnet: Das Werkzeug bricht ab,
|
||||||
|
wenn das Ergebnis schlechter waere als der alte Stand. */
|
||||||
|
[data-ton="1"] { --ton: #0284dd; } /* Dashboard */
|
||||||
|
[data-ton="2"] { --ton: #e15e8e; } /* Zahlen */
|
||||||
|
[data-ton="3"] { --ton: #ff9781; } /* LIVE */
|
||||||
|
[data-ton="4"] { --ton: #c26201; } /* Chat */
|
||||||
|
[data-ton="5"] { --ton: #febf12; } /* Schutz */
|
||||||
|
[data-ton="6"] { --ton: #768b09; } /* Community */
|
||||||
|
[data-ton="7"] { --ton: #089868; } /* Reports */
|
||||||
|
[data-ton="8"] { --ton: #c06ad0; } /* Kalender */
|
||||||
|
[data-ton="9"] { --ton: #afde57; } /* Bibliothek */
|
||||||
|
[data-ton="10"] { --ton: #7bba3a; } /* Steckbrief */
|
||||||
|
[data-ton="11"] { --ton: #ed8712; } /* Personen */
|
||||||
|
[data-ton="12"] { --ton: #01edb5; } /* Content */
|
||||||
|
[data-ton="13"] { --ton: #18bfa3; } /* Aufgaben */
|
||||||
|
[data-ton="14"] { --ton: #c0a311; } /* Technik */
|
||||||
|
[data-ton="15"] { --ton: #5fddff; } /* Calls */
|
||||||
|
[data-ton="16"] { --ton: #0da2c6; } /* Start-Check */
|
||||||
|
[data-ton="17"] { --ton: #bac8fc; } /* Agentur */
|
||||||
|
[data-ton="18"] { --ton: #9299fd; } /* Automationen */
|
||||||
|
[data-ton="19"] { --ton: #796ce0; } /* Dateien */
|
||||||
|
[data-ton="20"] { --ton: #fc88db; } /* Creator-Profile */
|
||||||
|
[data-ton="21"] { --ton: #fdb0d2; } /* Scout */
|
||||||
|
|
||||||
/* Rückfallwert für alles, was einen Ton braucht, aber keine Nummer hat.
|
/* Rückfallwert für alles, was einen Ton braucht, aber keine Nummer hat.
|
||||||
Steht bewusst in :where() -- damit zählt die Zeile beim Wettstreit der
|
Steht bewusst in :where() -- damit zählt die Zeile beim Wettstreit der
|
||||||
|
|||||||
Reference in New Issue
Block a user