Der Notizblock -- und eine Pruefung, die zwoelf Kacheln nie angesehen hat

Filipe: „fuer die modis, rechte und linke hand und dogfather eine
kachel hinzufuegst, sie soll: Notizen, heissen. ich will dass du das
auch wie ein notizblock erstellst. ich will dass es uebelst geil und
einzigartig ist."

=================================================================
TEIL 1: DIE FARBE -- UND WAS DABEI AUFFIEL
=================================================================

Fuer die neue Kachel braucht es einen Ton. Beim Suchen fiel auf, dass
tools/kachelton-entzerren.mjs zwoelf der 45 Farben fuer FREI hielt.

Sie sind es nicht. bereicheFuer() gibt fuer die fuenf Rollen des
Agenturhauses null zurueck -- das heisst „nimm die Liste aus dem
Browser", und die steht in workspace/assets/js/bereiche.js. Dort
stehen die Toene 1 bis 22 und 44: Dashboard, Zahlen, Team-Lage,
Scout-Pipeline. Kacheln, die jeden Tag jemand ansieht.

pruef-kachelfarben hatte denselben blinden Fleck. Sie meldete seit
Wochen „33 benutzte Toene" und war gruen. Was sie dadurch NICHT sah:

  * Der Farblauf von gestern Nacht hat zwoelf dieser Kacheln
    verschoben und drei davon unter die Buntheitsgrenze gedrueckt.
    Gruen geblieben.
  * Ton 7, 16 und 22 lagen SEIT JEHER unter der Grenze (0,112 / 0,116
    / 0,068). Nie gemeldet.

Das ist die Sorte gruener Haken, vor der die Hausregel warnt: Er sagt
nur, dass die Bedingung erfuellt war -- nicht, dass sie das Richtige
angesehen hat.

BEHOBEN, und zwar an der Wurzel: Die REGELN (Kontrast, Buntheit)
gelten jetzt fuer jeden Ton, der irgendwo an einer Kachel steht --
gelesen aus denselben fuenf Dateien, die auch pruef-kachel-universum
liest. Dazu die Namen der Kacheln, damit „Ton 1 traegt Dashboard"
ueberhaupt pruefbar ist.

DIE PALETTE WURDE NEU GERECHNET, mit dem dritten Verfahren an einem
Tag -- die ersten zwei stehen als Fehler im Kopf des Werkzeugs:

  1. Alle 45 neu verteilt. Lief ueber zwei ausdrueckliche Wuensche
     hinweg (#ff1a1a, #a8d8ff).
  2. Nur die Kollisionen, aber immer nur EINEN der beiden bewegt.
     Ergebnis: Ton 1 sprang vom Tuerkis ins Altrosa, 0,197 weit.
  3. BEIDE duerfen sich bewegen, und zwar beide nur ein bisschen.
     Zwei Toene, die 0,02 auseinanderstehen und 0,09 brauchen, teilen
     sich das -- jeder rueckt 0,045, und beide bleiben, was sie waren.

  kleinster Abstand   0,0154  ->  0,0905
  zu blass                 4  ->  0
  sichtbar veraendert           7 Kacheln (ueber 0,05)
  kaum zu sehen                28 (13 zwischen 0,02 und 0,05, 15 darunter)

EINE AUSNAHME MIT ZAHL, keine mit Achselzucken: Ton 1 (Dashboard)
bleibt acht Tausendstel unter der Buntheitsgrenze. Gemessen ueber ALLE
16,7 Millionen sRGB-Farben ist die naechste, die alle Regeln haelt,
#7a75c8 -- ein Blauviolett, 0,113 entfernt. Tuerkis erreicht in sRGB
schlicht keine hoehere Buntheit, und das schmale Band teilen sich
schon sieben Toene. Eine Kachel, die ihre Farbfamilie behaelt, ist die
bessere Antwort auf „richtig geil und speziell" als eine, die acht
Tausendstel bunter ist und niemand wiedererkennt. blassBis sagt jetzt
bei jeder Ausnahme, WIE WEIT sie reicht -- vorher hiess blassErlaubt
schlicht „hier wird weggesehen".

DIE NEUE FARBE IST GRUEN UND WOLLTE BERNSTEIN SEIN. Gesucht war die
Farbe von Papier. Gemessen gibt es sie nicht mehr: Der beste Bernstein
im ganzen Farbraum haelt 0,0799 Abstand -- unter der Hausgrenze. Und
Platz schaffen hilft nicht: Setzt man ihn fest, muss ein warmer Ton
das Band verlassen (Ton 45 waere 0,212 weit ins Magenta gewandert).
Die groesste wirklich freie Luecke liegt im Gruen, bei 0,0917. Ein
linierter Block in Gruen ist Papier, seit es Papier gibt.

=================================================================
TEIL 2: DER BLOCK
=================================================================

DREI ENTSCHEIDUNGEN, AUS DENEN DER REST FOLGT:

1. ES GIBT KEINEN SPEICHERN-KNOPF. Nirgends. Wer einen Block
   aufschlaegt und lostippt, drueckt hinterher nicht auf „sichern";
   er klappt ihn zu. Gesichert wird 800 ms nach dem letzten
   Tastendruck. Geht das nicht, bleibt der Text im Browser liegen und
   wird nachgereicht -- und der Fuss sagt ehrlich „noch nicht
   gesichert", statt einen Verlust zu melden, den man gerade nicht
   verhindern kann.

2. DAS BLATT IST EIN SCHREIBFELD MIT EINER DECKSCHICHT. Das Feld
   traegt Text und Cursor und ist unsichtbar; darueber zeichnet eine
   zweite Schicht denselben Text noch einmal -- mit Kaestchen,
   Ueberschriften, Strichen und Links.

   DARAUS FOLGT EINE EISERNE REGEL, und sie steht dreimal im Code:
   KEINE Auszeichnung darf die BREITE eines Zeichens aendern. Kein
   Fettdruck, keine andere Groesse, keine Sperrung. Erlaubt sind nur
   Farbe, Flaeche, Rahmen und Durchstreichen. Ein einziges
   font-weight: 700 verschoebe den Umbruch, und ab der zweiten Zeile
   stuende der sichtbare Text neben dem Cursor.

   pruef-notizen misst das am echten Umbruch: eine Probe mit allen
   Auszeichnungen und einer Zeile, die umbrechen MUSS. Sind beide
   Schichten verschieden hoch, sitzt der Umbruch woanders. Mit
   Gegenprobe -- Fettdruck auf der Deckschicht bricht die Deckung um
   genau eine Zeile (30 px), und die Zeile wird rot.

3. DIE TASTATUR FUEHRT DIE LISTE FORT, NICHT EINE LEISTE. Ein
   Spiegelstrich und Enter macht den naechsten Punkt, ein leeres
   Kaestchen und Enter das naechste Kaestchen -- und ein GESETZTER
   Haken wird dabei nicht mitgenommen, die naechste Aufgabe ist ja
   noch nicht erledigt. Zweimal Enter beendet die Liste. Ein Klick
   aufs Kaestchen hakt ab. Es gibt keine Werkzeugleiste, weil man beim
   Schreiben nie den Stift wechselt.

UND: NIEMAND SIEHT DIE NOTIZEN EINES ANDEREN. Auch DogFather nicht.
Es gibt in workspace-notizen.js keinen siehtAlles()-Zweig, keinen
Umschalter, keine fremde Liste -- jede Abfrage hat person_id = ? fest
eingebaut. Die Kachel steht deshalb in „Fuer dich" und nicht in
„Taeglich": Die Gruppe sagt die wichtigste Eigenschaft, bevor man sie
anfasst.

NUR AUF DER TEAM-ADRESSE. Auf der Agenturadresse ist die Seite ein
404 -- auch fuer DogFather. Das ist dieselbe Trennung wie bei Chat,
Kalender und Aufgaben, und sie steht an zwei Stellen: in
GEHOERT_ZU_ADRESSE (die Datei) und in der Schnittstelle (die Daten).
Die Seite ist nur HTML; die Daten sind die Sache.

Dazu: sechs Papierfarben, Anheften, Suche mit Hervorhebung im Text,
Papierkorb mit dreissig Tagen und einem Eintrag in der
Aufbewahrungsliste (ohne den waere die Frist ein Satz in einem
Kommentar -- genau das ist dem Support am 24.09. passiert).

=================================================================
EIN FUND BEIM BAUEN, DER ALLEN GEHOERT
=================================================================

DELETE /workspace/api/notizen/:id gab es schon -- in
workspace-aufgaben.js, fuer die Notizen AN einer Aufgabe. Weil jener
Router frueher eingehaengt ist, hat er jeden Loeschversuch des Blocks
abgefangen und mit 404 beantwortet.

Von aussen sah das aus wie ein Fehler im neuen Modul: Anlegen ging,
Aendern ging, Loeschen nicht. Man sucht dann im eigenen Code, und dort
ist nichts. Express meldet so etwas nicht -- es nimmt die erste Route,
die passt, und schweigt ueber die zweite.

Der Block liegt jetzt unter /workspace/api/notizblock. Und
pruef-notizen geht seitdem den Routenbaum von express durch und meldet
jedes Paar aus Methode und Pfad, das zweimal vergeben ist. Gemessen:
307 Wege, keine Dublette. Die Pruefung gilt fuers ganze Haus, auch
wenn sie in dieser Datei steht -- hier ist sie gefunden worden.

Nebenbei berichtigt: pruef-crew-adresse verlangte von jedem Eintrag
der Haustafel HTTP 200 mit Inhalt. Das stimmte, solange dort nur
oeffentliche Dateien standen. notizen.html ist die erste Seite hinter
der Anmeldung -- sie antwortet mit 302, und das ist richtig. Gefragt
wird jetzt, was die Tafel wirklich meint: GIBT es das hier.

GEMESSEN, alles nach den Aenderungen:

  pruef-notizen             76 Punkte, 0 Fehler (neu)
  pruef-kachelfarben        31 Punkte, 0 Fehler (vorher 26)
  pruef-kachel-universum    13 Punkte, 0 Fehler
  pruef-crew-adresse       157 Punkte, 0 Fehler
  pruef-buehne             230 Punkte, 0 Fehler -- notizen.html neu in
                           der Liste, schlechtester Kontrast 6,73:1,
                           also 50 % ueber der Grenze
  pruef-rechtetafel, -haus-seiten, -struktur, -css-klassen,
  -jeder-hat-eine-seite, -workspace-seiten, -kachelraster,
  -rueckmeldung            alle 0 Fehler

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-26 12:42:59 +02:00
co-authored by Claude Opus 5
parent 69ba533cee
commit 150555bc33
62 changed files with 4503 additions and 978 deletions
+543
View File
@@ -0,0 +1,543 @@
/* =====================================================================
DER NOTIZBLOCK (26.09.2026)
=====================================================================
Filipe: „ich will dass du das auch wie ein notizblock erstellst.
ich will dass es übelst geil und einzigartig ist. mach wirklich das
krasseste und geilste notizblock auf der welt."
---------------------------------------------------------------------
DIE EINE REGEL, AN DER HIER ALLES HÄNGT
Das Blatt besteht aus ZWEI Schichten, die exakt aufeinanderliegen:
ein Schreibfeld (trägt Text und Cursor, ist aber unsichtbar) und
eine Deckschicht darüber (zeichnet denselben Text noch einmal, mit
Haken, Überschriften und Strichen).
Damit das aufgeht, muss jedes Zeichen in beiden Schichten an
derselben Stelle landen. Deshalb gilt in dieser Datei:
JEDE AUSZEICHNUNG DARF FARBE UND FLÄCHE ÄNDERN — NIE DIE BREITE.
Erlaubt: color, background, box-shadow, text-decoration, opacity,
text-shadow, border-radius.
Verboten: font-weight, font-size, font-family, letter-spacing,
word-spacing, font-style, text-transform, padding und margin an
allem, was IM Textfluss liegt.
Ein einziges `font-weight: 700` auf einer Überschrift verschiebt
die ganze Zeile um ein paar Pixel, und ab der zweiten Zeile steht
der sichtbare Text neben dem Cursor. Das sieht aus wie ein
Browserfehler und ist einer in dieser Datei. `pruef-notizen` misst
die Deckung deshalb an echten Bildpunkten nach.
Fettdruck gibt es trotzdem — aber im KOPF des Blattes, außerhalb
des Textflusses: Die erste Zeile steht dort groß als Titel. So
bekommt man die Wirkung, ohne die Deckung zu verlieren.
---------------------------------------------------------------------
WARUM DAS PAPIER DUNKEL IST
Ein cremeweißes Blatt auf einer dunklen Seite ist eine Lampe. Die
Hausregel heißt augenschonend, und sie gilt auch für Papier. Das
Papiergefühl trägt hier die STRUKTUR — Linien im Zeilenabstand, ein
Randstrich, die Perforation oben —, nicht die Helligkeit. Eine
schwarze Kladde ist auch ein Notizbuch.
Die gewählte Papierfarbe ist die TINTE: Sie färbt Linien, Rand,
Kante und Haken. Sechs Farben, alle über 4,5:1 gegen den Grund
(siehe notiz-tabellen.js), damit sie auch Text tragen können.
===================================================================== */
.notizblock {
/* Der Zeilenabstand ist die Maßeinheit dieser Seite: Er bestimmt die
Zeilenhöhe im Text UND den Abstand der Linien auf dem Papier. Eine
zweite Zahl wäre die, die beim nächsten Umbau auseinanderläuft --
und man sähe es sofort, weil die Schrift dann zwischen den Linien
schwimmt. */
--n-zeile: 30px;
--n-schrift: 15.5px;
--n-oben: 26px; /* Innenabstand oben, im Feld wie im Deck */
--n-links: 66px; /* bis zum Text -- links davon der Rand */
--n-rechts: 24px;
--n-randstrich: 46px; /* wo der senkrechte Randstrich sitzt */
--n-tinte: var(--ton, var(--akzent));
}
/* ---------------------------------------------------------------------
Kopfzeile der Seite
--------------------------------------------------------------------- */
.n-kopf { align-items: flex-end; gap: 16px; flex-wrap: wrap; }
.n-kopf__griffe { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.n-suche {
display: flex; align-items: center; gap: 8px;
padding: 0 12px; height: 40px;
background: var(--glas); border: 1px solid var(--rand);
border-radius: 999px;
}
.n-suche:focus-within { border-color: var(--akzent); }
.n-suche__zeichen {
width: 17px; height: 17px; flex: none;
fill: none; stroke: var(--text-leise); stroke-width: 1.9;
stroke-linecap: round;
}
.n-suche__feld {
border: 0; background: none; color: var(--text);
font: inherit; font-size: .92rem; width: 11ch; min-width: 0;
padding: 0; outline: none;
}
.n-suche__feld::placeholder { color: var(--text-still); }
/* Das X, das Chromium in ein Suchfeld setzt, ist auf Dunkel fast
unsichtbar und verschiebt beim Erscheinen den Text. Weg damit --
geleert wird mit der Escape-Taste, und das steht im Titel. */
.n-suche__feld::-webkit-search-cancel-button { display: none; }
.n-neu { white-space: nowrap; }
/* ---------------------------------------------------------------------
Der Raum: Stapel links, Blatt rechts
--------------------------------------------------------------------- */
.n-raum {
display: grid;
grid-template-columns: minmax(240px, 300px) minmax(0, 1fr);
gap: 18px;
align-items: start;
}
/* ---------------------------------------------------------------------
Der Stapel
--------------------------------------------------------------------- */
.n-stapel {
display: flex; flex-direction: column; gap: 10px;
position: sticky; top: 14px;
max-height: calc(100vh - 120px);
}
.n-stapel__kopf { display: flex; align-items: baseline; gap: 10px; }
.n-stapel__titel {
margin: 0; font-size: .78rem; font-weight: 700;
letter-spacing: .06em; text-transform: uppercase;
color: var(--text-still);
}
.n-zahl {
display: inline-block; min-width: 20px; padding: 1px 7px;
text-align: center; font-size: .74rem; font-weight: 700;
color: var(--text-leise);
background: rgba(255, 255, 255, .06); border-radius: 999px;
}
.n-korb-knopf {
margin-left: auto; display: flex; align-items: center; gap: 6px;
border: 0; background: none; color: var(--text-still);
font: inherit; font-size: .78rem; cursor: pointer; padding: 2px 4px;
}
.n-korb-knopf:hover { color: var(--text-leise); }
.n-liste {
list-style: none; margin: 0; padding: 0 2px 4px 0;
display: flex; flex-direction: column; gap: 8px;
overflow-y: auto; min-height: 0;
}
/* EIN BLATT IM STAPEL. Es sieht aus wie ein Zettel mit angeknickter
Ecke -- die Ecke ist die einzige Form, die man bei dieser Größe
noch als „Papier" liest. */
.n-zettel {
position: relative; width: 100%; text-align: left;
padding: 11px 13px 10px; border: 0; cursor: pointer;
font: inherit; color: inherit;
background: color-mix(in srgb, var(--n-tinte) 7%, var(--glas));
border-radius: 10px;
/* Der farbige Streifen links ist die Papierfarbe des Blattes. */
box-shadow:
inset 3px 0 0 color-mix(in srgb, var(--n-tinte) 70%, transparent),
inset 0 0 0 1px var(--rand);
transition: background-color .14s ease, box-shadow .14s ease;
}
.n-zettel:hover {
background: color-mix(in srgb, var(--n-tinte) 13%, var(--glas));
}
.n-zettel[aria-current="true"] {
background: color-mix(in srgb, var(--n-tinte) 17%, var(--glas));
box-shadow:
inset 3px 0 0 var(--n-tinte),
inset 0 0 0 1px color-mix(in srgb, var(--n-tinte) 40%, transparent);
}
/* Die angeknickte Ecke oben rechts. */
.n-zettel::after {
content: ""; position: absolute; top: 0; right: 0;
width: 14px; height: 14px; border-radius: 0 10px 0 6px;
background: linear-gradient(225deg,
color-mix(in srgb, var(--n-tinte) 30%, transparent) 50%, transparent 50%);
}
.n-zettel__titel {
display: block; font-size: .94rem; color: var(--text);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
padding-right: 16px;
}
.n-zettel__vor {
display: block; margin-top: 3px; font-size: .8rem; color: var(--text-still);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.n-zettel__fuss {
display: flex; align-items: center; gap: 8px; margin-top: 7px;
font-size: .72rem; color: var(--text-still);
}
.n-zettel__nadel { color: var(--n-tinte); }
.n-zettel__haken {
margin-left: auto; padding: 1px 6px; border-radius: 999px;
background: color-mix(in srgb, var(--n-tinte) 16%, transparent);
color: var(--text-leise);
}
.n-zettel__haken[data-fertig="ja"] {
background: color-mix(in srgb, var(--n-tinte) 30%, transparent);
color: var(--text);
}
.n-leer { margin: 6px 2px; color: var(--text-still); font-size: .88rem; }
.n-leer__knopf {
border: 0; background: none; padding: 0; font: inherit;
color: var(--akzent); text-decoration: underline; cursor: pointer;
}
/* =====================================================================
DAS BLATT
===================================================================== */
.n-blatt {
position: relative; display: flex; flex-direction: column;
min-height: 62vh;
background:
linear-gradient(to bottom,
color-mix(in srgb, var(--n-tinte) 6%, var(--glas-tief)),
var(--glas-tief) 180px);
border: 1px solid color-mix(in srgb, var(--n-tinte) 22%, var(--rand));
border-radius: 14px;
overflow: hidden;
}
/* DIE PERFORATION. Halbkreise, die in den oberen Rand gestanzt sind --
das Einzige an dieser Seite, das sofort „Block" sagt, bevor man ein
Wort gelesen hat. Sie sitzt IN der Kante und nicht darunter: Ein
Streifen mit Löchern darunter wäre eine Verzierung, eine gestanzte
Kante ist ein Blatt. */
.n-blatt__perforation {
position: absolute; top: 0; left: 0; right: 0; height: 11px; z-index: 4;
background:
radial-gradient(circle at 50% 0,
var(--tinte) 0 3.4px, transparent 3.8px) repeat-x 0 0 / 16px 11px,
linear-gradient(to bottom,
color-mix(in srgb, var(--n-tinte) 34%, transparent),
transparent);
pointer-events: none;
}
.n-blatt__kopf {
display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap;
padding: 18px 20px 12px;
border-bottom: 1px solid color-mix(in srgb, var(--n-tinte) 16%, transparent);
}
/* HIER steht der Fettdruck — außerhalb des Textflusses, deshalb
unbedenklich (siehe der Kopf dieser Datei). Der Titel ist die erste
Zeile des Blattes, live abgeleitet. */
.n-blatt__titel {
margin: 0; flex: 1 1 14ch; min-width: 0;
font-size: 1.32rem; font-weight: 700; line-height: 1.25;
color: var(--text);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.n-blatt__titel[data-leer="ja"] { color: var(--text-still); }
.n-blatt__griffe { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.n-haken-stand {
padding: 3px 9px; border-radius: 999px; font-size: .76rem;
color: var(--text-leise);
background: color-mix(in srgb, var(--n-tinte) 15%, transparent);
}
.n-haken-stand[data-fertig="ja"] {
color: var(--text);
background: color-mix(in srgb, var(--n-tinte) 32%, transparent);
}
.n-griff {
display: inline-flex; align-items: center; gap: 6px;
min-height: 34px; padding: 0 11px;
background: none; border: 1px solid var(--rand); border-radius: 9px;
color: var(--text-leise); font: inherit; font-size: .82rem; cursor: pointer;
transition: border-color .14s ease, color .14s ease, background-color .14s ease;
}
.n-griff svg {
width: 16px; height: 16px; flex: none;
fill: none; stroke: currentColor; stroke-width: 1.8;
stroke-linecap: round; stroke-linejoin: round;
}
.n-griff:hover { color: var(--text); border-color: var(--rand-hell); }
.n-griff[aria-pressed="true"] {
color: var(--n-tinte);
border-color: color-mix(in srgb, var(--n-tinte) 55%, transparent);
background: color-mix(in srgb, var(--n-tinte) 12%, transparent);
}
.n-griff--weg:hover { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 50%, transparent); }
/* Die sechs Papierfarben. Kreise, nicht Kästchen: Ein Kästchen sieht
aus wie etwas, das man ankreuzt. */
.n-farben { display: flex; align-items: center; gap: 5px; }
/* `flex: none` ist nicht Zierde: In einer Reihe mit `align-items:
center` dehnt sich ein Knopf sonst auf die Hoehe der Reihe -- im
Bildschirmfoto standen statt sechs Kreisen sechs Ovale da. */
.n-farbe {
flex: none; width: 22px; height: 22px; padding: 0; cursor: pointer;
border: 1px solid rgba(255, 255, 255, .16); border-radius: 50%;
background: var(--f);
transition: transform .12s ease, box-shadow .12s ease;
}
.n-farbe:hover { transform: scale(1.12); }
.n-farbe[aria-pressed="true"] {
box-shadow: 0 0 0 2px var(--tinte), 0 0 0 3.6px var(--f);
}
/* =====================================================================
DAS PAPIER — die zwei Schichten
ALLES, WAS HIER DOPPELT STEHT, MUSS DOPPELT GLEICH SEIN. Die
gemeinsamen Werte stehen deshalb in EINER Regel, die beide
Schichten trifft; was nur eine von beiden braucht (Cursorfarbe,
Zeigedurchlass), steht einzeln darunter.
===================================================================== */
.n-papier {
position: relative; flex: 1 1 auto; min-height: 320px;
overflow: auto;
/* Die Liniatur und der Randstrich. `local` sorgt dafür, dass sie
MIT dem Text scrollen -- ohne das bliebe das Papier stehen und
die Schrift liefe darüber hinweg. */
background-image:
repeating-linear-gradient(to bottom,
transparent 0 calc(var(--n-zeile) - 1px),
color-mix(in srgb, var(--n-tinte) 15%, transparent)
calc(var(--n-zeile) - 1px) var(--n-zeile)),
linear-gradient(to right,
transparent 0 var(--n-randstrich),
color-mix(in srgb, var(--n-tinte) 34%, transparent) var(--n-randstrich),
transparent calc(var(--n-randstrich) + 1px));
background-position: 0 calc(var(--n-oben) + 0.72 * var(--n-zeile)), 0 0;
background-attachment: local, local;
}
/* Die gemeinsame Schreibweise beider Schichten. */
.n-papier__feld,
.n-papier__deck {
font-family: inherit;
font-size: var(--n-schrift);
line-height: var(--n-zeile);
letter-spacing: normal;
word-spacing: normal;
font-weight: 400;
font-style: normal;
padding: var(--n-oben) var(--n-rechts) 60px var(--n-links);
margin: 0;
border: 0;
white-space: pre-wrap;
overflow-wrap: break-word;
word-break: normal;
tab-size: 2;
text-align: left;
}
.n-papier__feld {
position: relative; z-index: 2;
display: block; width: 100%; min-height: 100%;
resize: none; overflow: hidden;
background: transparent;
/* Der Text ist unsichtbar, der Cursor nicht -- gezeichnet wird von
der Deckschicht. */
color: transparent;
caret-color: var(--text);
outline: none;
}
/* Die Markierung muss sichtbar bleiben, obwohl die Schrift es nicht
ist: Sie liegt UNTER der Deckschicht und färbt deren Grund. */
.n-papier__feld::selection {
background: color-mix(in srgb, var(--n-tinte) 34%, transparent);
color: transparent;
}
.n-papier__deck {
position: absolute; inset: 0 0 auto 0; z-index: 3;
color: var(--text);
/* Die Schicht liegt OBEN, lässt aber alles durch -- sonst käme kein
Klick mehr ins Schreibfeld. Nur die Haken fangen ihn ab (unten). */
pointer-events: none;
}
/* ---------- Die Auszeichnungen -- Farbe und Fläche, nie Breite ------ */
/* ÜBERSCHRIFT (`# `). Kein Fettdruck, keine größere Schrift -- beides
würde die Zeile verschieben. Stattdessen: die volle Textfarbe, ein
feiner Schein und eine Linie darunter. Auf dunklem Grund liest sich
das genauso als Überschrift, und die Deckung bleibt. */
.n-h {
color: #fff;
text-shadow: 0 0 10px color-mix(in srgb, var(--n-tinte) 55%, transparent);
box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--n-tinte) 45%, transparent);
}
.n-marke { color: color-mix(in srgb, var(--n-tinte) 70%, var(--text-still)); }
/* HAKEN. Die eckigen Klammern bleiben als Zeichen stehen (sonst
verschöbe sich die Zeile), werden aber unsichtbar gefärbt; darüber
liegt ein gezeichnetes Kästchen aus einem Rahmen, der keine Fläche
beansprucht. */
/* DAS KAESTCHEN WIRD GEZEICHNET, NICHT GERAHMT.
Der erste Anlauf legte einen Innenrahmen (`box-shadow: inset`) um
das Stueck Text. Das nimmt die Form des STUECKS an -- und ein
Stueck Text ist so hoch wie die Zeile (30 px) und so breit wie
drei Zeichen. Im Bildschirmfoto standen deshalb hochkant stehende
Pillen da, keine Kaestchen.
Jetzt zeichnet ein Pseudo-Element ein echtes Quadrat, mittig ueber
dem Stueck. Es ist absolut gesetzt und beansprucht damit keinen
Platz -- die Deckung mit dem Schreibfeld bleibt unberuehrt, genau
wie die Regel im Kopf dieser Datei es verlangt. */
.n-hk {
position: relative;
color: transparent;
cursor: pointer;
pointer-events: auto;
}
.n-hk::before {
content: ""; position: absolute;
left: 50%; top: 50%; transform: translate(-50%, -50%);
width: 16px; height: 16px;
border: 1.7px solid color-mix(in srgb, var(--n-tinte) 78%, transparent);
border-radius: 4px;
transition: border-color .12s ease, background-color .12s ease;
}
.n-hk:hover::before { border-color: var(--n-tinte); }
.n-hk[data-an="ja"]::before {
background: var(--n-tinte);
border-color: var(--n-tinte);
}
/* Der Haken selbst -- ebenfalls absolut. */
.n-hk[data-an="ja"]::after {
content: ""; position: absolute;
left: 50%; top: 50%;
width: 4px; height: 8.5px;
margin: -1px 0 0 -2px;
border: solid #07080d; border-width: 0 2.2px 2.2px 0;
transform: translate(-50%, -60%) rotate(42deg);
}
/* Eine erledigte Zeile wird leiser und durchgestrichen. */
.n-erledigt { color: var(--text-still); text-decoration: line-through; }
/* AUFZÄHLUNG, ZITAT, LINK */
.n-punkt { color: var(--n-tinte); }
.n-zitat {
color: var(--text-leise);
box-shadow: inset 2px 0 0 color-mix(in srgb, var(--n-tinte) 60%, transparent);
}
.n-link {
color: var(--akzent);
text-decoration: underline;
text-decoration-color: color-mix(in srgb, var(--akzent) 55%, transparent);
text-underline-offset: 2px;
pointer-events: auto; cursor: pointer;
}
/* Der Treffer der Suche. Nur Fläche -- Breite unverändert. */
.n-treffer {
background: color-mix(in srgb, var(--warn) 40%, transparent);
border-radius: 3px;
}
/* ---------- Der Fuß des Blattes -------------------------------------- */
.n-blatt__fuss {
display: flex; align-items: center; gap: 12px;
padding: 9px 20px 11px;
border-top: 1px solid color-mix(in srgb, var(--n-tinte) 14%, transparent);
font-size: .78rem; color: var(--text-still);
}
.n-wann { margin-left: auto; }
.n-stand { display: inline-flex; align-items: center; gap: 7px; }
/* Der Punkt vor dem Wort. Er ist die eigentliche Auskunft -- man liest
die Farbe, bevor man das Wort liest. */
.n-stand::before {
content: ""; width: 7px; height: 7px; border-radius: 50%;
background: color-mix(in srgb, var(--n-tinte) 70%, transparent);
}
.n-stand[data-stand="sichert"]::before { background: var(--akzent); }
.n-stand[data-stand="offen"]::before { background: var(--warn); }
.n-stand[data-stand="offen"] { color: var(--warn); }
/* ---------- Der Papierkorb ------------------------------------------- */
.n-korb {
background: var(--glas-tief); border: 1px solid var(--rand);
border-radius: 14px; padding: 18px 20px 20px;
}
.n-korb__kopf { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.n-korb__kopf .knopf-still { margin-left: auto; }
.n-korb__satz { margin: 8px 0 14px; color: var(--text-still); font-size: .86rem; }
.n-liste--korb { max-height: none; }
.n-korb-zeile {
display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
padding: 11px 13px;
background: var(--glas); border: 1px solid var(--rand); border-radius: 10px;
}
.n-korb-zeile__text { flex: 1 1 16ch; min-width: 0; }
.n-korb-zeile__titel {
display: block; color: var(--text-leise);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.n-korb-zeile__wann { display: block; font-size: .76rem; color: var(--text-still); }
/* =====================================================================
HANDY: eine Spalte, das Blatt legt sich darüber
=====================================================================
Gemessen wird das Haus bei 320, 390 und 430 px. Zwei Spalten
bräuchten dort jede rund 160 px -- ein Blatt Papier, auf das keine
Zeile passt. Also: Der Stapel IST die Seite, und ein Blatt legt sich
davor. Zurück geht es mit dem Knopf der Kopfleiste, den es auf jeder
Seite des Hauses gibt.
===================================================================== */
@media (max-width: 860px) {
.notizblock { --n-links: 52px; --n-randstrich: 34px; --n-rechts: 16px; }
.n-raum { grid-template-columns: minmax(0, 1fr); }
.n-stapel { position: static; max-height: none; }
.n-blatt, .n-korb {
position: fixed; inset: 0; z-index: 60;
border-radius: 0; border-left: 0; border-right: 0;
min-height: 0; overflow: auto;
/* Unter der Kopfleiste beginnen und über der Fußzeile enden --
sonst liegt das Blatt über dem Zurück-Knopf, und man kommt aus
ihm nicht mehr heraus. */
padding-top: calc(env(safe-area-inset-top, 0px) + 58px);
padding-bottom: env(safe-area-inset-bottom, 0px);
background: var(--tinte);
}
.n-blatt { display: flex; }
.n-blatt__perforation { top: calc(env(safe-area-inset-top, 0px) + 58px); }
.n-blatt__kopf { padding: 14px 16px 10px; }
.n-blatt__titel { font-size: 1.15rem; }
.n-griff__text { display: none; }
.n-griff { padding: 0 9px; }
/* Das Suchfeld nimmt, was uebrig ist. Mit einer festen Breite von
acht Zeichen stand im Bildschirmfoto „Suche" mit abgeschnittenem
n da -- ein Feld, das seinen eigenen Namen nicht fasst. */
.n-suche { flex: 1 1 150px; min-width: 0; }
.n-suche__feld { width: 100%; }
.n-kopf__griffe { width: 100%; }
/* Die Tastatur des Handys verdeckt sonst die letzte Zeile. */
.n-papier__feld, .n-papier__deck { padding-bottom: 45vh; }
}
/* Wer weniger Bewegung möchte, bekommt keine. */
@media (prefers-reduced-motion: reduce) {
.n-zettel, .n-griff, .n-farbe { transition: none; }
.n-farbe:hover { transform: none; }
}
/* Im Kontrastmodus trägt allein die Kante. Verläufe und
Farbmischungen würden dort zu grauen Flächen. */
@media (forced-colors: active) {
.n-papier { background-image: none; }
.n-blatt__perforation { display: none; }
.n-hk { box-shadow: none; border: 1px solid CanvasText; color: CanvasText; }
.n-hk[data-an="ja"] { background: Highlight; }
}
+216 -138
View File
@@ -888,10 +888,10 @@ body.start > main { position: relative; z-index: 1; }
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: #aa6c90; } /* Dashboard */
[data-ton="2"] { --ton: #d050a4; } /* Zahlen */
[data-ton="1"] { --ton: #0093b1; } /* Dashboard */
[data-ton="2"] { --ton: #e76c9f; } /* Zahlen */
[data-ton="3"] { --ton: #18b803; } /* LIVE */
[data-ton="4"] { --ton: #c16302; } /* Chat */
[data-ton="4"] { --ton: #b46f1e; } /* Chat */
/* TON 5 IST VON HAND GESETZT (09.09.2026, screen4).
Filipe: "ich will dass die kategorie eine farbe bekommt die extrem
@@ -925,22 +925,22 @@ body.start > main { position: relative; z-index: 1; }
gewünscht ("alle kacheln auf der ganzen website sollen sein wie auf
der startseite"). Was sie heraushebt, ist die Farbe, nicht eine
Sonderform. */
[data-ton="5"] { --ton: #fe04d8; } /* Schutz & Regeln -- Wunsch: extrem auffällig */
[data-ton="6"] { --ton: #b699fe; } /* Community */
[data-ton="7"] { --ton: #098f71; } /* Reports */
[data-ton="8"] { --ton: #ab68ff; } /* Kalender */
[data-ton="9"] { --ton: #ceae6e; } /* Bibliothek */
[data-ton="10"] { --ton: #fe667e; } /* Steckbrief */
[data-ton="11"] { --ton: #da8a06; } /* Personen */
[data-ton="12"] { --ton: #7eca8c; } /* Content */
[data-ton="5"] { --ton: #fc33fc; } /* Schutz & Regeln -- Wunsch: extrem auffällig */
[data-ton="6"] { --ton: #c6a5ff; } /* Community */
[data-ton="7"] { --ton: #009369; } /* Reports */
[data-ton="8"] { --ton: #ab60ff; } /* Kalender */
[data-ton="9"] { --ton: #e703c6; } /* Bibliothek */
[data-ton="10"] { --ton: #ff7269; } /* Steckbrief */
[data-ton="11"] { --ton: #db8a0c; } /* Personen */
[data-ton="12"] { --ton: #0fd88a; } /* Content */
[data-ton="13"] { --ton: #12b37e; } /* Aufgaben */
[data-ton="14"] { --ton: #ffbd4a; } /* Technik */
[data-ton="15"] { --ton: #9ce6bc; } /* Calls */
[data-ton="16"] { --ton: #28aeba; } /* Start-Check */
[data-ton="14"] { --ton: #ffb742; } /* Technik */
[data-ton="15"] { --ton: #00d5fc; } /* Calls */
[data-ton="16"] { --ton: #00b1d5; } /* Start-Check */
[data-ton="17"] { --ton: #cace02; } /* Agentur */
[data-ton="18"] { --ton: #9a86ca; } /* Automationen */
[data-ton="19"] { --ton: #08eb9f; } /* Dateien */
[data-ton="20"] { --ton: #d48a92; } /* Creator-Profile */
[data-ton="18"] { --ton: #a28aff; } /* Automationen */
[data-ton="19"] { --ton: #00f9a8; } /* Dateien */
[data-ton="20"] { --ton: #ba7bc3; } /* Creator-Profile */
/* TON 21 IST VON HAND GESETZT (09.09.2026, screen6).
Filipe: "die farbe von dieser kategorie soll knall rot sein."
@@ -958,7 +958,7 @@ body.start > main { position: relative; z-index: 1; }
Kontrast auf dem dunklen Grund: 5,01:1 (noetig 4,5). Von sechs
geprueften Rottoenen hat dieser den groessten Abstand UND genug
Kontrast -- deshalb dieser und nicht der erstbeste. */
[data-ton="21"] { --ton: #d6525e; } /* Scout-Pipeline -- Wunsch: knallrot */
[data-ton="21"] { --ton: #db4b66; } /* Scout-Pipeline -- Wunsch: knallrot */
/* TON 22 -- TEAM-LAGE (09.09.2026).
@@ -975,13 +975,13 @@ body.start > main { position: relative; z-index: 1; }
Warm und hell ist kein Zufall: Diese eine Kachel gehoert der
Leitung. Zwischen den kuehlen Toenen der Arbeitsbereiche faellt ein
warmer auf, ohne lauter zu sein. */
[data-ton="22"] { --ton: #fee0b2; } /* Team-Lage */
[data-ton="22"] { --ton: #ffde7b; } /* Team-Lage */
/* Ton 23 (10.09.2026, Ideen-Board). NICHT nach Gefuehl gewaehlt: Die
22 vorhandenen Toene sind dicht besetzt, deshalb wurde der Abstand zu
allen ausgerechnet. #5f8a9f hat mit 127 den zweitgroessten -- der
groesste war ein Graubraun, und eine graue Kachel zwischen bunten
sieht abgeschaltet aus. Gedeckt statt grell, wie alles hier. */
[data-ton="23"] { --ton: #08b5fc; } /* Ideen-Board */
[data-ton="23"] { --ton: #75b7ff; } /* Ideen-Board */
/* Ton 24 (10.09.2026, Eingang). Wieder gerechnet statt gewaehlt, und bei
23 belegten Toenen wird es eng: Der Abstand im CIELAB-Raum zum
@@ -994,7 +994,7 @@ body.start > main { position: relative; z-index: 1; }
schlechtesten zu fokussieren. Kraeftiges Gruen kam auf 39,5, hat aber
L* 81 und wuerde leuchten. Dieser Ton hat eine Leuchtdichte von 0,109
-- dunkel genug, um neben den anderen ruhig zu bleiben. */
[data-ton="24"] { --ton: #d21afd; } /* Eingang */
[data-ton="24"] { --ton: #cf1bfc; } /* Eingang */
/* TON 25 -- Rueckmeldung (10.09.2026).
@@ -1032,8 +1032,8 @@ body.start > main { position: relative; z-index: 1; }
Wenn eine achtundzwanzigste Kachel kommt, ist das der Zeitpunkt, an
dem die Kacheln Gruppenfarben brauchen statt Einzelfarben -- nicht
der, an dem man den naechsten Ton dazwischenquetscht. */
[data-ton="26"] { --ton: #07fff1; } /* Eure Aufgaben (bis 19.09. "Entwicklung") */
[data-ton="27"] { --ton: #e871ff; } /* Talente */
[data-ton="26"] { --ton: #0cfff0; } /* Eure Aufgaben (bis 19.09. "Entwicklung") */
[data-ton="27"] { --ton: #e472ff; } /* Talente */
/* =====================================================================
DIE SIEBEN TOENE DES TREFFS (11.09.2026)
@@ -1059,12 +1059,12 @@ body.start > main { position: relative; z-index: 1; }
Hand gewaehlte Farbfamilie im selben Blau bei 12,6. Die Rechnung war
also nicht Genauigkeitsspielerei, sondern der Unterschied zwischen
unterscheidbar und nicht. */
[data-ton="28"] { --ton: #069511; } /* Regeln & Hilfe */
[data-ton="29"] { --ton: #f10778; } /* Highlights */
[data-ton="30"] { --ton: #a7ff98; } /* Wunschliste */
[data-ton="31"] { --ton: #0ed1d0; } /* Was ansteht */
[data-ton="32"] { --ton: #0687bc; } /* Mitmachen */
[data-ton="33"] { --ton: #f4acff; } /* Anschlagbrett */
[data-ton="28"] { --ton: #03960f; } /* Regeln & Hilfe */
[data-ton="29"] { --ton: #f60684; } /* Highlights */
[data-ton="30"] { --ton: #abff96; } /* Wunschliste */
[data-ton="31"] { --ton: #2dc9ba; } /* Was ansteht */
[data-ton="32"] { --ton: #577ecc; } /* Mitmachen */
[data-ton="33"] { --ton: #ffabff; } /* Anschlagbrett */
[data-ton="34"] { --ton: #a2af10; } /* Der Treff */
/* DER 35. TON (11.09.2026) — für „Wer sieht was".
@@ -1091,7 +1091,7 @@ body.start > main { position: relative; z-index: 1; }
mit einer anderen geteilt. Zwei Dinge in einer Farbe heißt, dass die
Farbe nichts mehr sagt — und pruef-start-ansicht zählt genau das
nach ("34 Farben auf 35 Kacheln" war ihre Meldung). */
[data-ton="35"] { --ton: #fe8ebe; } /* Wer sieht was */
[data-ton="35"] { --ton: #ff90c0; } /* Wer sieht was */
/* Der 36., nach derselben Rechnung: #7c2771, Abstand 29,7 zu den beiden
nächsten (#8e5b77 „Highlights", #2f2f7f „Wer sieht was"). Auch das
liegt über dem schwächsten Paar der vorhandenen Töne (24,9). */
@@ -1131,11 +1131,11 @@ body.start > main { position: relative; z-index: 1; }
sind. Mehr ist im lesbaren Bereich nicht zu holen; wer den naechsten
hinzufuegt, sollte das wissen und notfalls einen alten verschieben,
statt die Schwelle stillschweigend zu senken. */
[data-ton="39"] { --ton: #ff57ba; } /* Vertraulich melden */
[data-ton="40"] { --ton: #ff9d82; } /* Entwicklung (die Auswertung) */
[data-ton="41"] { --ton: #6c8efe; } /* Treff-Chat */
[data-ton="39"] { --ton: #ff51c9; } /* Vertraulich melden */
[data-ton="40"] { --ton: #ff9f7b; } /* Entwicklung (die Auswertung) */
[data-ton="41"] { --ton: #5499ff; } /* Treff-Chat */
[data-ton="42"] { --ton: #918a01; } /* Material (Platzhalter, wird gerechnet) */
[data-ton="43"] { --ton: #027afb; } /* Calls & Protokolle (gerechnet 22.09.2026) */
[data-ton="43"] { --ton: #007bff; } /* Calls & Protokolle (gerechnet 22.09.2026) */
/* GEWUENSCHT UND NACHGEMESSEN (25.09.2026)
Filipe zum Bildschirmfoto der Support-Seite: „ich will dass du bitte
@@ -1178,7 +1178,8 @@ body.start > main { position: relative; z-index: 1; }
(vorher hielt es eine zu blasse Farbe, weil ihr Abstand gut war).
Wer diesen Ton spaeter aufhellen will, muss den Abstand neu rechnen
-- nicht nach Gefuehl nachbessern. */
[data-ton="45"] { --ton: #fe660e; } /* Unterstützen (gerechnet 24.09.2026) */
[data-ton="45"] { --ton: #e76006; } /* Unterstützen (gerechnet 24.09.2026) */
[data-ton="46"] { --ton: #9cde90; } /* Notizen */
/* WARUM ES KEINEN TON 46 GIBT (25.09.2026)
Die Tuer ins andere Haus trug bis heute Ton 40 -- dieselbe Nummer
@@ -8533,8 +8534,8 @@ body.start .k-leiste .feldschild::after {
/* ---- ANFANG GERECHNET: kachel-regenbogen.mjs ---- */
/* ERZEUGT VON tools/kachel-regenbogen.mjs — NICHT VON HAND ÄNDERN.
99 Punkte in drei Größen, jede Größe einmal alle
33 benutzten Kacheltöne. Die Liste ist aus den
102 Punkte in drei Größen, jede Größe einmal alle
34 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. */
@@ -8562,113 +8563,116 @@ body.start .k-leiste .feldschild::after {
/* 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. */
radial-gradient(4.82px 4.82px at 21.3px 22.8px, color-mix(in srgb, #07fff1 88%, #ffffff), color-mix(in srgb, #07fff1 88%, #ffffff) 34%, color-mix(in srgb, #07fff1 55%, transparent) 52%, transparent 100%),
radial-gradient(5.48px 5.48px at 50.6px 7.0px, color-mix(in srgb, #fe8ebe 88%, #ffffff), color-mix(in srgb, #fe8ebe 88%, #ffffff) 34%, color-mix(in srgb, #fe8ebe 55%, transparent) 52%, transparent 100%),
radial-gradient(6.57px 6.57px at 81.2px 6.7px, color-mix(in srgb, #72d512 88%, #ffffff), color-mix(in srgb, #72d512 88%, #ffffff) 34%, color-mix(in srgb, #72d512 55%, transparent) 52%, transparent 100%),
radial-gradient(4.71px 4.71px at 110.3px 23.0px, color-mix(in srgb, #6c8efe 88%, #ffffff), color-mix(in srgb, #6c8efe 88%, #ffffff) 34%, color-mix(in srgb, #6c8efe 55%, transparent) 52%, transparent 100%),
radial-gradient(4.88px 4.88px at 154.3px 8.0px, color-mix(in srgb, #c16302 88%, #ffffff), color-mix(in srgb, #c16302 88%, #ffffff) 34%, color-mix(in srgb, #c16302 55%, transparent) 52%, transparent 100%),
radial-gradient(4.83px 4.83px at 185.7px 11.5px, color-mix(in srgb, #12b37e 88%, #ffffff), color-mix(in srgb, #12b37e 88%, #ffffff) 34%, color-mix(in srgb, #12b37e 55%, transparent) 52%, transparent 100%),
radial-gradient(4.33px 4.33px at 16.5px 42.0px, color-mix(in srgb, #ff57ba 88%, #ffffff), color-mix(in srgb, #ff57ba 88%, #ffffff) 34%, color-mix(in srgb, #ff57ba 55%, transparent) 52%, transparent 100%),
radial-gradient(5.17px 5.17px at 49.7px 52.7px, color-mix(in srgb, #a2af10 88%, #ffffff), color-mix(in srgb, #a2af10 88%, #ffffff) 34%, color-mix(in srgb, #a2af10 55%, transparent) 52%, transparent 100%),
radial-gradient(4.63px 4.63px at 82.5px 47.6px, color-mix(in srgb, #027afb 88%, #ffffff), color-mix(in srgb, #027afb 88%, #ffffff) 34%, color-mix(in srgb, #027afb 55%, transparent) 52%, transparent 100%),
radial-gradient(4.55px 4.55px at 105.1px 54.1px, color-mix(in srgb, #fe660e 88%, #ffffff), color-mix(in srgb, #fe660e 88%, #ffffff) 34%, color-mix(in srgb, #fe660e 55%, transparent) 52%, transparent 100%),
radial-gradient(6.07px 6.07px at 149.6px 56.3px, color-mix(in srgb, #08eb9f 88%, #ffffff), color-mix(in srgb, #08eb9f 88%, #ffffff) 34%, color-mix(in srgb, #08eb9f 55%, transparent) 52%, transparent 100%),
radial-gradient(4.34px 4.34px at 182.8px 53.3px, color-mix(in srgb, #f4acff 88%, #ffffff), color-mix(in srgb, #f4acff 88%, #ffffff) 34%, color-mix(in srgb, #f4acff 55%, transparent) 52%, transparent 100%),
radial-gradient(5.58px 5.58px at 14.4px 89.8px, color-mix(in srgb, #cace02 88%, #ffffff), color-mix(in srgb, #cace02 88%, #ffffff) 34%, color-mix(in srgb, #cace02 55%, transparent) 52%, transparent 100%),
radial-gradient(4.55px 4.55px at 56.4px 81.9px, color-mix(in srgb, #a8d8ff 88%, #ffffff), color-mix(in srgb, #a8d8ff 88%, #ffffff) 34%, color-mix(in srgb, #a8d8ff 55%, transparent) 52%, transparent 100%),
radial-gradient(5.65px 5.65px at 78.8px 88.9px, color-mix(in srgb, #ff9d82 88%, #ffffff), color-mix(in srgb, #ff9d82 88%, #ffffff) 34%, color-mix(in srgb, #ff9d82 55%, transparent) 52%, transparent 100%),
radial-gradient(4.85px 4.85px at 113.3px 78.4px, color-mix(in srgb, #069511 88%, #ffffff), color-mix(in srgb, #069511 88%, #ffffff) 34%, color-mix(in srgb, #069511 55%, transparent) 52%, transparent 100%),
radial-gradient(4.40px 4.40px at 142.4px 90.0px, color-mix(in srgb, #e871ff 88%, #ffffff), color-mix(in srgb, #e871ff 88%, #ffffff) 34%, color-mix(in srgb, #e871ff 55%, transparent) 52%, transparent 100%),
radial-gradient(4.65px 4.65px at 190.5px 85.8px, color-mix(in srgb, #eeef08 88%, #ffffff), color-mix(in srgb, #eeef08 88%, #ffffff) 34%, color-mix(in srgb, #eeef08 55%, transparent) 52%, transparent 100%),
radial-gradient(6.19px 6.19px at 20.7px 123.9px, color-mix(in srgb, #08b5fc 88%, #ffffff), color-mix(in srgb, #08b5fc 88%, #ffffff) 34%, color-mix(in srgb, #08b5fc 55%, transparent) 52%, transparent 100%),
radial-gradient(5.44px 5.44px at 56.7px 116.6px, color-mix(in srgb, #ff1a1a 88%, #ffffff), color-mix(in srgb, #ff1a1a 88%, #ffffff) 34%, color-mix(in srgb, #ff1a1a 55%, transparent) 52%, transparent 100%),
radial-gradient(6.28px 6.28px at 89.2px 122.9px, color-mix(in srgb, #18b803 88%, #ffffff), color-mix(in srgb, #18b803 88%, #ffffff) 34%, color-mix(in srgb, #18b803 55%, transparent) 52%, transparent 100%),
radial-gradient(4.52px 4.52px at 105.6px 108.9px, color-mix(in srgb, #d21afd 88%, #ffffff), color-mix(in srgb, #d21afd 88%, #ffffff) 34%, color-mix(in srgb, #d21afd 55%, transparent) 52%, transparent 100%),
radial-gradient(4.31px 4.31px at 150.4px 108.8px, color-mix(in srgb, #918a01 88%, #ffffff), color-mix(in srgb, #918a01 88%, #ffffff) 34%, color-mix(in srgb, #918a01 55%, transparent) 52%, transparent 100%),
radial-gradient(5.83px 5.83px at 189.4px 117.4px, color-mix(in srgb, #0687bc 88%, #ffffff), color-mix(in srgb, #0687bc 88%, #ffffff) 34%, color-mix(in srgb, #0687bc 55%, transparent) 52%, transparent 100%),
radial-gradient(4.38px 4.38px at 10.9px 139.1px, color-mix(in srgb, #fe667e 88%, #ffffff), color-mix(in srgb, #fe667e 88%, #ffffff) 34%, color-mix(in srgb, #fe667e 55%, transparent) 52%, transparent 100%),
radial-gradient(6.29px 6.29px at 54.7px 155.9px, color-mix(in srgb, #a7ff98 88%, #ffffff), color-mix(in srgb, #a7ff98 88%, #ffffff) 34%, color-mix(in srgb, #a7ff98 55%, transparent) 52%, transparent 100%),
radial-gradient(6.12px 6.12px at 75.1px 154.0px, color-mix(in srgb, #ab68ff 88%, #ffffff), color-mix(in srgb, #ab68ff 88%, #ffffff) 34%, color-mix(in srgb, #ab68ff 55%, transparent) 52%, transparent 100%),
radial-gradient(6.08px 6.08px at 123.3px 154.7px, color-mix(in srgb, #ffbd4a 88%, #ffffff), color-mix(in srgb, #ffbd4a 88%, #ffffff) 34%, color-mix(in srgb, #ffbd4a 55%, transparent) 52%, transparent 100%),
radial-gradient(5.81px 5.81px at 141.2px 142.7px, color-mix(in srgb, #0ed1d0 88%, #ffffff), color-mix(in srgb, #0ed1d0 88%, #ffffff) 34%, color-mix(in srgb, #0ed1d0 55%, transparent) 52%, transparent 100%),
radial-gradient(4.42px 4.42px at 177.4px 144.5px, color-mix(in srgb, #f10778 88%, #ffffff), color-mix(in srgb, #f10778 88%, #ffffff) 34%, color-mix(in srgb, #f10778 55%, transparent) 52%, transparent 100%),
radial-gradient(5.16px 5.16px at 17.7px 183.1px, color-mix(in srgb, #61ff09 88%, #ffffff), color-mix(in srgb, #61ff09 88%, #ffffff) 34%, color-mix(in srgb, #61ff09 55%, transparent) 52%, transparent 100%),
radial-gradient(4.80px 4.80px at 46.6px 182.2px, color-mix(in srgb, #b699fe 88%, #ffffff), color-mix(in srgb, #b699fe 88%, #ffffff) 34%, color-mix(in srgb, #b699fe 55%, transparent) 52%, transparent 100%),
radial-gradient(5.00px 5.00px at 80.1px 179.7px, color-mix(in srgb, #da8a06 88%, #ffffff), color-mix(in srgb, #da8a06 88%, #ffffff) 34%, color-mix(in srgb, #da8a06 55%, transparent) 52%, transparent 100%),
radial-gradient(4.82px 4.82px at 21.3px 22.8px, color-mix(in srgb, #2dc9ba 88%, #ffffff), color-mix(in srgb, #2dc9ba 88%, #ffffff) 34%, color-mix(in srgb, #2dc9ba 55%, transparent) 52%, transparent 100%),
radial-gradient(5.48px 5.48px at 50.6px 7.0px, color-mix(in srgb, #ffabff 88%, #ffffff), color-mix(in srgb, #ffabff 88%, #ffffff) 34%, color-mix(in srgb, #ffabff 55%, transparent) 52%, transparent 100%),
radial-gradient(6.57px 6.57px at 81.2px 6.7px, color-mix(in srgb, #918a01 88%, #ffffff), color-mix(in srgb, #918a01 88%, #ffffff) 34%, color-mix(in srgb, #918a01 55%, transparent) 52%, transparent 100%),
radial-gradient(4.71px 4.71px at 110.3px 23.0px, color-mix(in srgb, #12b37e 88%, #ffffff), color-mix(in srgb, #12b37e 88%, #ffffff) 34%, color-mix(in srgb, #12b37e 55%, transparent) 52%, transparent 100%),
radial-gradient(4.88px 4.88px at 154.3px 8.0px, color-mix(in srgb, #e472ff 88%, #ffffff), color-mix(in srgb, #e472ff 88%, #ffffff) 34%, color-mix(in srgb, #e472ff 55%, transparent) 52%, transparent 100%),
radial-gradient(4.83px 4.83px at 185.7px 11.5px, color-mix(in srgb, #ffb742 88%, #ffffff), color-mix(in srgb, #ffb742 88%, #ffffff) 34%, color-mix(in srgb, #ffb742 55%, transparent) 52%, transparent 100%),
radial-gradient(4.33px 4.33px at 16.5px 42.0px, color-mix(in srgb, #00f9a8 88%, #ffffff), color-mix(in srgb, #00f9a8 88%, #ffffff) 34%, color-mix(in srgb, #00f9a8 55%, transparent) 52%, transparent 100%),
radial-gradient(5.17px 5.17px at 49.7px 52.7px, color-mix(in srgb, #cf1bfc 88%, #ffffff), color-mix(in srgb, #cf1bfc 88%, #ffffff) 34%, color-mix(in srgb, #cf1bfc 55%, transparent) 52%, transparent 100%),
radial-gradient(4.63px 4.63px at 82.5px 47.6px, color-mix(in srgb, #db8a0c 88%, #ffffff), color-mix(in srgb, #db8a0c 88%, #ffffff) 34%, color-mix(in srgb, #db8a0c 55%, transparent) 52%, transparent 100%),
radial-gradient(4.55px 4.55px at 105.1px 54.1px, color-mix(in srgb, #03960f 88%, #ffffff), color-mix(in srgb, #03960f 88%, #ffffff) 34%, color-mix(in srgb, #03960f 55%, transparent) 52%, transparent 100%),
radial-gradient(6.07px 6.07px at 149.6px 56.3px, color-mix(in srgb, #ab60ff 88%, #ffffff), color-mix(in srgb, #ab60ff 88%, #ffffff) 34%, color-mix(in srgb, #ab60ff 55%, transparent) 52%, transparent 100%),
radial-gradient(4.34px 4.34px at 182.8px 53.3px, color-mix(in srgb, #b46f1e 88%, #ffffff), color-mix(in srgb, #b46f1e 88%, #ffffff) 34%, color-mix(in srgb, #b46f1e 55%, transparent) 52%, transparent 100%),
radial-gradient(5.58px 5.58px at 14.4px 89.8px, color-mix(in srgb, #18b803 88%, #ffffff), color-mix(in srgb, #18b803 88%, #ffffff) 34%, color-mix(in srgb, #18b803 55%, transparent) 52%, transparent 100%),
radial-gradient(4.55px 4.55px at 56.4px 81.9px, color-mix(in srgb, #c6a5ff 88%, #ffffff), color-mix(in srgb, #c6a5ff 88%, #ffffff) 34%, color-mix(in srgb, #c6a5ff 55%, transparent) 52%, transparent 100%),
radial-gradient(5.65px 5.65px at 78.8px 88.9px, color-mix(in srgb, #e76006 88%, #ffffff), color-mix(in srgb, #e76006 88%, #ffffff) 34%, color-mix(in srgb, #e76006 55%, transparent) 52%, transparent 100%),
radial-gradient(4.85px 4.85px at 113.3px 78.4px, color-mix(in srgb, #9cde90 88%, #ffffff), color-mix(in srgb, #9cde90 88%, #ffffff) 34%, color-mix(in srgb, #9cde90 55%, transparent) 52%, transparent 100%),
radial-gradient(4.40px 4.40px at 142.4px 90.0px, color-mix(in srgb, #577ecc 88%, #ffffff), color-mix(in srgb, #577ecc 88%, #ffffff) 34%, color-mix(in srgb, #577ecc 55%, transparent) 52%, transparent 100%),
radial-gradient(4.65px 4.65px at 190.5px 85.8px, color-mix(in srgb, #ff9f7b 88%, #ffffff), color-mix(in srgb, #ff9f7b 88%, #ffffff) 34%, color-mix(in srgb, #ff9f7b 55%, transparent) 52%, transparent 100%),
radial-gradient(6.19px 6.19px at 20.7px 123.9px, color-mix(in srgb, #abff96 88%, #ffffff), color-mix(in srgb, #abff96 88%, #ffffff) 34%, color-mix(in srgb, #abff96 55%, transparent) 52%, transparent 100%),
radial-gradient(5.44px 5.44px at 56.7px 116.6px, color-mix(in srgb, #5499ff 88%, #ffffff), color-mix(in srgb, #5499ff 88%, #ffffff) 34%, color-mix(in srgb, #5499ff 55%, transparent) 52%, transparent 100%),
radial-gradient(6.28px 6.28px at 89.2px 122.9px, color-mix(in srgb, #ff1a1a 88%, #ffffff), color-mix(in srgb, #ff1a1a 88%, #ffffff) 34%, color-mix(in srgb, #ff1a1a 55%, transparent) 52%, transparent 100%),
radial-gradient(4.52px 4.52px at 105.6px 108.9px, color-mix(in srgb, #61ff09 88%, #ffffff), color-mix(in srgb, #61ff09 88%, #ffffff) 34%, color-mix(in srgb, #61ff09 55%, transparent) 52%, transparent 100%),
radial-gradient(4.31px 4.31px at 150.4px 108.8px, color-mix(in srgb, #007bff 88%, #ffffff), color-mix(in srgb, #007bff 88%, #ffffff) 34%, color-mix(in srgb, #007bff 55%, transparent) 52%, transparent 100%),
radial-gradient(5.83px 5.83px at 189.4px 117.4px, color-mix(in srgb, #ff7269 88%, #ffffff), color-mix(in srgb, #ff7269 88%, #ffffff) 34%, color-mix(in srgb, #ff7269 55%, transparent) 52%, transparent 100%),
radial-gradient(4.38px 4.38px at 10.9px 139.1px, color-mix(in srgb, #72d512 88%, #ffffff), color-mix(in srgb, #72d512 88%, #ffffff) 34%, color-mix(in srgb, #72d512 55%, transparent) 52%, transparent 100%),
radial-gradient(6.29px 6.29px at 54.7px 155.9px, color-mix(in srgb, #75b7ff 88%, #ffffff), color-mix(in srgb, #75b7ff 88%, #ffffff) 34%, color-mix(in srgb, #75b7ff 55%, transparent) 52%, transparent 100%),
radial-gradient(6.12px 6.12px at 75.1px 154.0px, color-mix(in srgb, #f60684 88%, #ffffff), color-mix(in srgb, #f60684 88%, #ffffff) 34%, color-mix(in srgb, #f60684 55%, transparent) 52%, transparent 100%),
radial-gradient(6.08px 6.08px at 123.3px 154.7px, color-mix(in srgb, #a2af10 88%, #ffffff), color-mix(in srgb, #a2af10 88%, #ffffff) 34%, color-mix(in srgb, #a2af10 55%, transparent) 52%, transparent 100%),
radial-gradient(5.81px 5.81px at 141.2px 142.7px, color-mix(in srgb, #a8d8ff 88%, #ffffff), color-mix(in srgb, #a8d8ff 88%, #ffffff) 34%, color-mix(in srgb, #a8d8ff 55%, transparent) 52%, transparent 100%),
radial-gradient(4.42px 4.42px at 177.4px 144.5px, color-mix(in srgb, #ff90c0 88%, #ffffff), color-mix(in srgb, #ff90c0 88%, #ffffff) 34%, color-mix(in srgb, #ff90c0 55%, transparent) 52%, transparent 100%),
radial-gradient(5.16px 5.16px at 17.7px 183.1px, color-mix(in srgb, #cace02 88%, #ffffff), color-mix(in srgb, #cace02 88%, #ffffff) 34%, color-mix(in srgb, #cace02 55%, transparent) 52%, transparent 100%),
radial-gradient(4.80px 4.80px at 46.6px 182.2px, color-mix(in srgb, #0cfff0 88%, #ffffff), color-mix(in srgb, #0cfff0 88%, #ffffff) 34%, color-mix(in srgb, #0cfff0 55%, transparent) 52%, transparent 100%),
radial-gradient(5.00px 5.00px at 80.1px 179.7px, color-mix(in srgb, #ff51c9 88%, #ffffff), color-mix(in srgb, #ff51c9 88%, #ffffff) 34%, color-mix(in srgb, #ff51c9 55%, transparent) 52%, transparent 100%),
radial-gradient(6.47px 6.47px at 116.3px 173.0px, color-mix(in srgb, #eeef08 88%, #ffffff), color-mix(in srgb, #eeef08 88%, #ffffff) 34%, color-mix(in srgb, #eeef08 55%, transparent) 52%, transparent 100%),
/* 4. DIE MITTLEREN — die Masse des Feldes. */
radial-gradient(3.21px 3.21px at 5.1px 5.6px, color-mix(in srgb, #07fff1 82%, #ffffff), color-mix(in srgb, #07fff1 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.60px 3.60px at 30.9px 16.4px, color-mix(in srgb, #ab68ff 82%, #ffffff), color-mix(in srgb, #ab68ff 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.00px 3.00px at 52.4px 11.5px, color-mix(in srgb, #ff1a1a 82%, #ffffff), color-mix(in srgb, #ff1a1a 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.86px 2.86px at 82.4px 14.9px, color-mix(in srgb, #cace02 82%, #ffffff), color-mix(in srgb, #cace02 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.96px 2.96px at 98.8px 9.3px, color-mix(in srgb, #12b37e 82%, #ffffff), color-mix(in srgb, #12b37e 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.65px 2.65px at 121.0px 14.2px, color-mix(in srgb, #b699fe 82%, #ffffff), color-mix(in srgb, #b699fe 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.49px 2.49px at 11.8px 26.8px, color-mix(in srgb, #fe667e 82%, #ffffff), color-mix(in srgb, #fe667e 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.92px 2.92px at 33.8px 37.3px, color-mix(in srgb, #eeef08 82%, #ffffff), color-mix(in srgb, #eeef08 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.47px 2.47px at 55.5px 38.6px, color-mix(in srgb, #08eb9f 82%, #ffffff), color-mix(in srgb, #08eb9f 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.55px 2.55px at 74.8px 28.0px, color-mix(in srgb, #6c8efe 82%, #ffffff), color-mix(in srgb, #6c8efe 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.43px 2.43px at 103.9px 38.4px, color-mix(in srgb, #f10778 82%, #ffffff), color-mix(in srgb, #f10778 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.62px 2.62px at 124.3px 27.3px, color-mix(in srgb, #918a01 82%, #ffffff), color-mix(in srgb, #918a01 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.61px 2.61px at 6.8px 55.0px, color-mix(in srgb, #069511 82%, #ffffff), color-mix(in srgb, #069511 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.19px 3.19px at 36.1px 60.1px, color-mix(in srgb, #027afb 82%, #ffffff), color-mix(in srgb, #027afb 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.63px 3.63px at 56.8px 55.5px, color-mix(in srgb, #fe8ebe 82%, #ffffff), color-mix(in srgb, #fe8ebe 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.62px 2.62px at 82.6px 57.2px, color-mix(in srgb, #ffbd4a 82%, #ffffff), color-mix(in srgb, #ffbd4a 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.89px 2.89px at 97.7px 59.0px, color-mix(in srgb, #18b803 82%, #ffffff), color-mix(in srgb, #18b803 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.29px 3.29px at 125.8px 57.6px, color-mix(in srgb, #a8d8ff 82%, #ffffff), color-mix(in srgb, #a8d8ff 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.52px 2.52px at 10.9px 83.0px, color-mix(in srgb, #ff57ba 82%, #ffffff), color-mix(in srgb, #ff57ba 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.37px 2.37px at 33.1px 72.6px, color-mix(in srgb, #da8a06 82%, #ffffff), color-mix(in srgb, #da8a06 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.21px 3.21px at 54.6px 78.8px, color-mix(in srgb, #a7ff98 82%, #ffffff), color-mix(in srgb, #a7ff98 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.43px 3.43px at 71.0px 71.9px, color-mix(in srgb, #08b5fc 82%, #ffffff), color-mix(in srgb, #08b5fc 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.16px 3.16px at 92.6px 70.6px, color-mix(in srgb, #f4acff 82%, #ffffff), color-mix(in srgb, #f4acff 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.22px 3.22px at 118.9px 78.1px, color-mix(in srgb, #c16302 82%, #ffffff), color-mix(in srgb, #c16302 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.50px 2.50px at 16.0px 101.8px, color-mix(in srgb, #61ff09 82%, #ffffff), color-mix(in srgb, #61ff09 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.80px 2.80px at 33.8px 102.3px, color-mix(in srgb, #0687bc 82%, #ffffff), color-mix(in srgb, #0687bc 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.52px 2.52px at 49.8px 93.9px, color-mix(in srgb, #e871ff 82%, #ffffff), color-mix(in srgb, #e871ff 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.87px 2.87px at 77.0px 95.8px, color-mix(in srgb, #fe660e 82%, #ffffff), color-mix(in srgb, #fe660e 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.37px 2.37px at 101.9px 95.3px, color-mix(in srgb, #72d512 82%, #ffffff), color-mix(in srgb, #72d512 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.42px 3.42px at 113.5px 91.8px, color-mix(in srgb, #0ed1d0 82%, #ffffff), color-mix(in srgb, #0ed1d0 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.22px 3.22px at 12.7px 116.5px, color-mix(in srgb, #d21afd 82%, #ffffff), color-mix(in srgb, #d21afd 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.35px 2.35px at 26.5px 120.2px, color-mix(in srgb, #ff9d82 82%, #ffffff), color-mix(in srgb, #ff9d82 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.34px 3.34px at 53.1px 119.0px, color-mix(in srgb, #a2af10 82%, #ffffff), color-mix(in srgb, #a2af10 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.21px 3.21px at 5.1px 5.6px, color-mix(in srgb, #2dc9ba 82%, #ffffff), color-mix(in srgb, #2dc9ba 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.60px 3.60px at 30.9px 16.4px, color-mix(in srgb, #cf1bfc 82%, #ffffff), color-mix(in srgb, #cf1bfc 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.00px 3.00px at 52.4px 11.5px, color-mix(in srgb, #e76006 82%, #ffffff), color-mix(in srgb, #e76006 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.86px 2.86px at 82.4px 14.9px, color-mix(in srgb, #61ff09 82%, #ffffff), color-mix(in srgb, #61ff09 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.96px 2.96px at 98.8px 9.3px, color-mix(in srgb, #a8d8ff 82%, #ffffff), color-mix(in srgb, #a8d8ff 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.65px 2.65px at 121.0px 14.2px, color-mix(in srgb, #ffabff 82%, #ffffff), color-mix(in srgb, #ffabff 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.49px 2.49px at 11.8px 26.8px, color-mix(in srgb, #db8a0c 82%, #ffffff), color-mix(in srgb, #db8a0c 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.92px 2.92px at 33.8px 37.3px, color-mix(in srgb, #9cde90 82%, #ffffff), color-mix(in srgb, #9cde90 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.47px 2.47px at 55.5px 38.6px, color-mix(in srgb, #007bff 82%, #ffffff), color-mix(in srgb, #007bff 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.55px 2.55px at 74.8px 28.0px, color-mix(in srgb, #ff90c0 82%, #ffffff), color-mix(in srgb, #ff90c0 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.43px 2.43px at 103.9px 38.4px, color-mix(in srgb, #918a01 82%, #ffffff), color-mix(in srgb, #918a01 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.62px 2.62px at 124.3px 27.3px, color-mix(in srgb, #03960f 82%, #ffffff), color-mix(in srgb, #03960f 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.61px 2.61px at 6.8px 55.0px, color-mix(in srgb, #577ecc 82%, #ffffff), color-mix(in srgb, #577ecc 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.19px 3.19px at 36.1px 60.1px, color-mix(in srgb, #ff7269 82%, #ffffff), color-mix(in srgb, #ff7269 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.63px 3.63px at 56.8px 55.5px, color-mix(in srgb, #cace02 82%, #ffffff), color-mix(in srgb, #cace02 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.62px 2.62px at 82.6px 57.2px, color-mix(in srgb, #12b37e 82%, #ffffff), color-mix(in srgb, #12b37e 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.89px 2.89px at 97.7px 59.0px, color-mix(in srgb, #ab60ff 82%, #ffffff), color-mix(in srgb, #ab60ff 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.29px 3.29px at 125.8px 57.6px, color-mix(in srgb, #ff9f7b 82%, #ffffff), color-mix(in srgb, #ff9f7b 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.52px 2.52px at 10.9px 83.0px, color-mix(in srgb, #72d512 82%, #ffffff), color-mix(in srgb, #72d512 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.37px 2.37px at 33.1px 72.6px, color-mix(in srgb, #0cfff0 82%, #ffffff), color-mix(in srgb, #0cfff0 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.21px 3.21px at 54.6px 78.8px, color-mix(in srgb, #e472ff 82%, #ffffff), color-mix(in srgb, #e472ff 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.43px 3.43px at 71.0px 71.9px, color-mix(in srgb, #b46f1e 82%, #ffffff), color-mix(in srgb, #b46f1e 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.16px 3.16px at 92.6px 70.6px, color-mix(in srgb, #abff96 82%, #ffffff), color-mix(in srgb, #abff96 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.22px 3.22px at 118.9px 78.1px, color-mix(in srgb, #75b7ff 82%, #ffffff), color-mix(in srgb, #75b7ff 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.50px 2.50px at 16.0px 101.8px, color-mix(in srgb, #ff51c9 82%, #ffffff), color-mix(in srgb, #ff51c9 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.80px 2.80px at 33.8px 102.3px, color-mix(in srgb, #ffb742 82%, #ffffff), color-mix(in srgb, #ffb742 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.52px 2.52px at 49.8px 93.9px, color-mix(in srgb, #18b803 82%, #ffffff), color-mix(in srgb, #18b803 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.87px 2.87px at 77.0px 95.8px, color-mix(in srgb, #5499ff 82%, #ffffff), color-mix(in srgb, #5499ff 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.37px 2.37px at 101.9px 95.3px, color-mix(in srgb, #f60684 82%, #ffffff), color-mix(in srgb, #f60684 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.42px 3.42px at 113.5px 91.8px, color-mix(in srgb, #eeef08 82%, #ffffff), color-mix(in srgb, #eeef08 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.22px 3.22px at 12.7px 116.5px, color-mix(in srgb, #00f9a8 82%, #ffffff), color-mix(in srgb, #00f9a8 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.35px 2.35px at 26.5px 120.2px, color-mix(in srgb, #c6a5ff 82%, #ffffff), color-mix(in srgb, #c6a5ff 82%, #ffffff) 34%, transparent 100%),
radial-gradient(3.34px 3.34px at 53.1px 119.0px, color-mix(in srgb, #ff1a1a 82%, #ffffff), color-mix(in srgb, #ff1a1a 82%, #ffffff) 34%, transparent 100%),
radial-gradient(2.84px 2.84px at 72.3px 120.1px, color-mix(in srgb, #a2af10 82%, #ffffff), color-mix(in srgb, #a2af10 82%, #ffffff) 34%, transparent 100%),
/* 5. DER FEINE STAUB. Er füllt die Lücken: Ein Feld aus nur
großen Punkten sieht aus wie aufgeklebt. */
radial-gradient(1.70px 1.70px at 6.2px 11.0px, color-mix(in srgb, #07fff1 74%, #ffffff), color-mix(in srgb, #07fff1 74%, #ffffff) 34%, transparent 100%),
radial-gradient(2.00px 2.00px at 19.0px 4.4px, color-mix(in srgb, #b699fe 74%, #ffffff), color-mix(in srgb, #b699fe 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.88px 1.88px at 38.0px 4.6px, color-mix(in srgb, #f10778 74%, #ffffff), color-mix(in srgb, #f10778 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.78px 1.78px at 49.4px 2.8px, color-mix(in srgb, #ffbd4a 74%, #ffffff), color-mix(in srgb, #ffbd4a 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.68px 1.68px at 65.7px 3.2px, color-mix(in srgb, #a7ff98 74%, #ffffff), color-mix(in srgb, #a7ff98 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.75px 1.75px at 77.7px 7.7px, color-mix(in srgb, #0687bc 74%, #ffffff), color-mix(in srgb, #0687bc 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.44px 1.44px at 3.7px 23.9px, color-mix(in srgb, #d21afd 74%, #ffffff), color-mix(in srgb, #d21afd 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.64px 1.64px at 23.7px 21.2px, color-mix(in srgb, #ff1a1a 74%, #ffffff), color-mix(in srgb, #ff1a1a 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.53px 1.53px at 30.8px 17.1px, color-mix(in srgb, #eeef08 74%, #ffffff), color-mix(in srgb, #eeef08 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.69px 1.69px at 46.6px 21.7px, color-mix(in srgb, #069511 74%, #ffffff), color-mix(in srgb, #069511 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.70px 1.70px at 6.2px 11.0px, color-mix(in srgb, #2dc9ba 74%, #ffffff), color-mix(in srgb, #2dc9ba 74%, #ffffff) 34%, transparent 100%),
radial-gradient(2.00px 2.00px at 19.0px 4.4px, color-mix(in srgb, #c6a5ff 74%, #ffffff), color-mix(in srgb, #c6a5ff 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.88px 1.88px at 38.0px 4.6px, color-mix(in srgb, #f60684 74%, #ffffff), color-mix(in srgb, #f60684 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.78px 1.78px at 49.4px 2.8px, color-mix(in srgb, #ffb742 74%, #ffffff), color-mix(in srgb, #ffb742 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.68px 1.68px at 65.7px 3.2px, color-mix(in srgb, #abff96 74%, #ffffff), color-mix(in srgb, #abff96 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.75px 1.75px at 77.7px 7.7px, color-mix(in srgb, #0cfff0 74%, #ffffff), color-mix(in srgb, #0cfff0 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.44px 1.44px at 3.7px 23.9px, color-mix(in srgb, #ab60ff 74%, #ffffff), color-mix(in srgb, #ab60ff 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.64px 1.64px at 23.7px 21.2px, color-mix(in srgb, #ff7269 74%, #ffffff), color-mix(in srgb, #ff7269 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.53px 1.53px at 30.8px 17.1px, color-mix(in srgb, #918a01 74%, #ffffff), color-mix(in srgb, #918a01 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.69px 1.69px at 46.6px 21.7px, color-mix(in srgb, #9cde90 74%, #ffffff), color-mix(in srgb, #9cde90 74%, #ffffff) 34%, transparent 100%),
radial-gradient(2.03px 2.03px at 64.3px 18.5px, color-mix(in srgb, #a8d8ff 74%, #ffffff), color-mix(in srgb, #a8d8ff 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.36px 1.36px at 77.2px 20.7px, color-mix(in srgb, #f4acff 74%, #ffffff), color-mix(in srgb, #f4acff 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.40px 1.40px at 3.8px 31.4px, color-mix(in srgb, #fe660e 74%, #ffffff), color-mix(in srgb, #fe660e 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.36px 1.36px at 17.0px 32.1px, color-mix(in srgb, #a2af10 74%, #ffffff), color-mix(in srgb, #a2af10 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.84px 1.84px at 32.1px 35.9px, color-mix(in srgb, #12b37e 74%, #ffffff), color-mix(in srgb, #12b37e 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.63px 1.63px at 48.3px 34.6px, color-mix(in srgb, #6c8efe 74%, #ffffff), color-mix(in srgb, #6c8efe 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.47px 1.47px at 61.7px 38.8px, color-mix(in srgb, #fe8ebe 74%, #ffffff), color-mix(in srgb, #fe8ebe 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.59px 1.59px at 73.2px 36.6px, color-mix(in srgb, #da8a06 74%, #ffffff), color-mix(in srgb, #da8a06 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.34px 1.34px at 2.9px 51.3px, color-mix(in srgb, #61ff09 74%, #ffffff), color-mix(in srgb, #61ff09 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.78px 1.78px at 17.6px 52.6px, color-mix(in srgb, #0ed1d0 74%, #ffffff), color-mix(in srgb, #0ed1d0 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.72px 1.72px at 35.6px 46.1px, color-mix(in srgb, #ab68ff 74%, #ffffff), color-mix(in srgb, #ab68ff 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.87px 1.87px at 50.9px 49.3px, color-mix(in srgb, #fe667e 74%, #ffffff), color-mix(in srgb, #fe667e 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.44px 1.44px at 59.6px 52.2px, color-mix(in srgb, #918a01 74%, #ffffff), color-mix(in srgb, #918a01 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.77px 1.77px at 73.3px 49.9px, color-mix(in srgb, #18b803 74%, #ffffff), color-mix(in srgb, #18b803 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.76px 1.76px at 3.5px 65.0px, color-mix(in srgb, #08b5fc 74%, #ffffff), color-mix(in srgb, #08b5fc 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.72px 1.72px at 25.0px 58.0px, color-mix(in srgb, #e871ff 74%, #ffffff), color-mix(in srgb, #e871ff 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.77px 1.77px at 30.9px 65.9px, color-mix(in srgb, #ff9d82 74%, #ffffff), color-mix(in srgb, #ff9d82 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.87px 1.87px at 51.0px 65.4px, color-mix(in srgb, #cace02 74%, #ffffff), color-mix(in srgb, #cace02 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.38px 1.38px at 61.3px 65.1px, color-mix(in srgb, #08eb9f 74%, #ffffff), color-mix(in srgb, #08eb9f 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.79px 1.79px at 78.3px 63.7px, color-mix(in srgb, #027afb 74%, #ffffff), color-mix(in srgb, #027afb 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.34px 1.34px at 9.8px 74.9px, color-mix(in srgb, #ff57ba 74%, #ffffff), color-mix(in srgb, #ff57ba 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.54px 1.54px at 20.7px 75.9px, color-mix(in srgb, #c16302 74%, #ffffff), color-mix(in srgb, #c16302 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.61px 1.61px at 31.6px 74.4px, color-mix(in srgb, #72d512 74%, #ffffff), color-mix(in srgb, #72d512 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.36px 1.36px at 77.2px 20.7px, color-mix(in srgb, #cf1bfc 74%, #ffffff), color-mix(in srgb, #cf1bfc 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.40px 1.40px at 3.8px 31.4px, color-mix(in srgb, #ff1a1a 74%, #ffffff), color-mix(in srgb, #ff1a1a 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.36px 1.36px at 17.0px 32.1px, color-mix(in srgb, #eeef08 74%, #ffffff), color-mix(in srgb, #eeef08 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.84px 1.84px at 32.1px 35.9px, color-mix(in srgb, #18b803 74%, #ffffff), color-mix(in srgb, #18b803 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.63px 1.63px at 48.3px 34.6px, color-mix(in srgb, #75b7ff 74%, #ffffff), color-mix(in srgb, #75b7ff 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.47px 1.47px at 61.7px 38.8px, color-mix(in srgb, #e472ff 74%, #ffffff), color-mix(in srgb, #e472ff 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.59px 1.59px at 73.2px 36.6px, color-mix(in srgb, #ff9f7b 74%, #ffffff), color-mix(in srgb, #ff9f7b 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.34px 1.34px at 2.9px 51.3px, color-mix(in srgb, #cace02 74%, #ffffff), color-mix(in srgb, #cace02 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.78px 1.78px at 17.6px 52.6px, color-mix(in srgb, #03960f 74%, #ffffff), color-mix(in srgb, #03960f 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.72px 1.72px at 35.6px 46.1px, color-mix(in srgb, #007bff 74%, #ffffff), color-mix(in srgb, #007bff 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.87px 1.87px at 50.9px 49.3px, color-mix(in srgb, #ffabff 74%, #ffffff), color-mix(in srgb, #ffabff 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.44px 1.44px at 59.6px 52.2px, color-mix(in srgb, #e76006 74%, #ffffff), color-mix(in srgb, #e76006 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.77px 1.77px at 73.3px 49.9px, color-mix(in srgb, #a2af10 74%, #ffffff), color-mix(in srgb, #a2af10 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.76px 1.76px at 3.5px 65.0px, color-mix(in srgb, #00f9a8 74%, #ffffff), color-mix(in srgb, #00f9a8 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.72px 1.72px at 25.0px 58.0px, color-mix(in srgb, #5499ff 74%, #ffffff), color-mix(in srgb, #5499ff 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.77px 1.77px at 30.9px 65.9px, color-mix(in srgb, #ff51c9 74%, #ffffff), color-mix(in srgb, #ff51c9 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.87px 1.87px at 51.0px 65.4px, color-mix(in srgb, #b46f1e 74%, #ffffff), color-mix(in srgb, #b46f1e 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.38px 1.38px at 61.3px 65.1px, color-mix(in srgb, #72d512 74%, #ffffff), color-mix(in srgb, #72d512 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.79px 1.79px at 78.3px 63.7px, color-mix(in srgb, #12b37e 74%, #ffffff), color-mix(in srgb, #12b37e 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.34px 1.34px at 9.8px 74.9px, color-mix(in srgb, #577ecc 74%, #ffffff), color-mix(in srgb, #577ecc 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.54px 1.54px at 20.7px 75.9px, color-mix(in srgb, #ff90c0 74%, #ffffff), color-mix(in srgb, #ff90c0 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.61px 1.61px at 31.6px 74.4px, color-mix(in srgb, #db8a0c 74%, #ffffff), color-mix(in srgb, #db8a0c 74%, #ffffff) 34%, transparent 100%),
radial-gradient(1.97px 1.97px at 47.5px 78.3px, color-mix(in srgb, #61ff09 74%, #ffffff), color-mix(in srgb, #61ff09 74%, #ffffff) 34%, transparent 100%),
/* 6. EIN SCHRÄGER SCHLEIER quer über das Feld. Er bindet die
99 Punkte zu einer Fläche zusammen — ohne ihn
102 Punkte zu einer Fläche zusammen — ohne ihn
schweben sie einzeln. Sehr blass; er soll Richtung geben,
nicht auffallen. */
linear-gradient(104deg,
@@ -8682,11 +8686,11 @@ body.start .k-leiste .feldschild::after {
linear-gradient(180deg, #0b1020 0%, #070b14 100%);
background-size:
100% 100%, 100% 100%,
197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px,
197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 197px 197px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 131px 131px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px, 83px 83px,
100% 100%, 100% 100%;
background-repeat:
no-repeat, no-repeat,
repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat,
repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat, repeat,
no-repeat, no-repeat;
/* Die Kante trägt den Kachelton mit — sonst wäre ausgerechnet die
farbigste Kachel der Wand einfarbig umrandet. */
@@ -9078,3 +9082,77 @@ body[data-ton] .kopfleiste__rechts > *:hover {
.kopf-mehr { border: 1px solid CanvasText; }
.kopf-mehr__fach { border: 1px solid CanvasText; background: Canvas; }
}
/* =====================================================================
DIE NOTIZKACHEL IST EIN BLOCK (26.09.2026)
=====================================================================
Filipe: „ich will dass du das auch wie ein notizblock erstellst.
ich will dass es übelst geil und einzigartig ist."
Dieselbe Bauart wie `silber` bei den Aufgaben und `regenbogen` bei
Willkommen: ein MERKMAL am Element (`data-papier="ja"`), keine
Farbe. Die Farbwerkzeuge rechnen weiter mit `data-ton="46"`, und
wer diese Stilvorlage nicht bekommt (alter Zwischenspeicher,
Kontrastmodus), sieht die Kachel in ihrem Grün — erkennbar, nur
ohne Papier.
VIER EBENEN, von oben nach unten:
1. DIE PERFORATION. Eine Reihe dunkler Halbkreise dicht unter der
Oberkante — die Löcher, an denen man das Blatt abreißt. Sie
sitzen in einem hellen Streifen (dem Leimrand), sonst
schwebten sie im Nichts.
2. DIE LINIATUR. Waagerechte Linien im Ton der Kachel, sehr
schwach. Ihr Abstand ist 26 px und damit GRÖSSER als die
Zeilenhöhe des Kacheltextes: Läge eine Linie genau unter der
Schrift, sähe es aus wie eine Unterstreichung.
3. DER RANDSTRICH. Eine senkrechte Linie bei 14 % Breite, wie der
Rand auf Schulpapier. Er ist das Detail, an dem man „Papier"
erkennt, ohne es zu benennen.
4. DER GRUNDVERLAUF. Oben eine Spur heller — so liegt Licht auf
einem Block, der leicht gewölbt auf dem Tisch liegt.
WARUM DAS PAPIER DUNKEL IST. Ein cremeweißer Block auf einer
dunklen Wand wäre eine Lampe: Bei 95 % Leuchtdichte blendet die
Kachel, und die Hausregel heißt augenschonend. Das Papiergefühl
trägt hier die STRUKTUR (Löcher, Linien, Rand), nicht die
Helligkeit — genau wie eine schwarze Kladde immer noch als
Notizbuch zu erkennen ist.
===================================================================== */
.kachel[data-papier="ja"] {
background-image:
/* 1. Die Perforation: dunkle Löcher im hellen Leimrand. */
radial-gradient(circle at 50% 0,
color-mix(in srgb, #05070d 88%, transparent) 0 2.6px,
transparent 2.9px),
linear-gradient(color-mix(in srgb, var(--ton) 22%, transparent) 0 9px,
transparent 9px),
/* 2. Die Liniatur. */
repeating-linear-gradient(to bottom,
transparent 0 25px,
color-mix(in srgb, var(--ton) 13%, transparent) 25px 26px),
/* 3. Der Randstrich. */
linear-gradient(to right,
transparent 0 14%,
color-mix(in srgb, var(--ton) 26%, transparent) 14%,
transparent calc(14% + 1px)),
/* 4. Der Grundverlauf. */
linear-gradient(to bottom,
color-mix(in srgb, var(--ton) 13%, transparent),
color-mix(in srgb, var(--ton) 5%, transparent) 62%,
transparent);
background-size: 18px 9px, 100% 9px, 100% 100%, 100% 100%, 100% 100%;
background-position: 0 3px, 0 0, 0 0, 0 0, 0 0;
background-repeat: repeat-x, no-repeat, repeat, no-repeat, no-repeat;
}
/* Beim Anfassen wird die Liniatur eine Spur deutlicher — als würde man
das Blatt zum Licht drehen. Kein Blinken, keine Bewegung. */
@media (prefers-reduced-motion: no-preference) {
.kachel[data-papier="ja"] { transition: filter .2s ease; }
}
/* Im Kontrastmodus trägt allein die Kante. Ein Verlauf würde dort zu
einer grauen Fläche — dieselbe Entscheidung wie bei Silber. */
@media (forced-colors: active) {
.kachel[data-papier="ja"] { background-image: none; }
}
+17
View File
@@ -348,6 +348,23 @@
hin, man schliesst es nicht ein. */
linien: ['M4.9 14.4c0 2.9 3.18 4.9 7.1 4.9s7.1-2 7.1-4.9'],
},
/* NOTIZEN (26.09.2026) -- ein Block, kein Blatt.
Der Unterschied steckt im LEIMSTREIFEN oben: Ein Rechteck mit
Linien darin ist ein Dokument; erst der geschlossene Balken am
Kopf macht daraus einen Block, den man abreisst. Er ist
deshalb die Flaeche -- das Einzige, was bei zwanzig Bildpunkten
sicher ankommt.
DIE DRITTE LINIE IST KUERZER. Drei gleich lange Striche lesen
sich als Text; ein abbrechender Strich liest sich als
Handschrift, die mitten im Wort aufhoert. Das ist der
Unterschied zwischen „Dokument" und „Notiz", und er kostet
vier Bildpunkte. */
notizen: {
fuellung: ['M5 3.6h14v2.9H5z'],
linien: ['M5 3.6h14v16.8H5z', 'M8.2 10.4h7.6', 'M8.2 13.8h7.6', 'M8.2 17.2h4.4'],
},
hinaus: {
/* Nur der Pfeilkopf ist Flaeche -- er ist die Aussage. */
fuellung: ['M13.8 4H20v6.2z'],
+904
View File
@@ -0,0 +1,904 @@
/* =====================================================================
DER NOTIZBLOCK (26.09.2026)
=====================================================================
Filipe: „ich will dass du das auch wie ein notizblock erstellst.
ich will dass es übelst geil und einzigartig ist."
---------------------------------------------------------------------
DREI ENTSCHEIDUNGEN, AUS DENEN DER REST FOLGT
1. ES GIBT KEINEN SPEICHERN-KNOPF. Wer einen Block aufschlägt und
lostippt, drückt hinterher nicht auf „sichern"; er klappt ihn zu.
Gesichert wird 800 ms nach dem letzten Tastendruck. Geht das
nicht (Funkloch, Server weg), bleibt der Text im Browser liegen
und wird nachgereicht — und der Fuß sagt ehrlich, dass er noch
nicht drüben ist.
2. DAS BLATT IST EIN SCHREIBFELD, KEIN CONTENTEDITABLE. Die
Auszeichnung (Haken, Überschriften, Striche) zeichnet eine
Deckschicht darüber. Warum, steht im Kopf von notizen.css — kurz:
Ein `contenteditable` bricht beim Einfügen aus fremden
Programmen, beim Rückgängigmachen und an jeder Handytastatur,
die über eine Zwischenzeile schreibt.
3. DIE TASTATUR FÜHRT DIE LISTE FORT, NICHT EINE LEISTE. Eine
Werkzeugleiste über einem Notizblock ist eine Leiste zu viel:
Man schreibt `- ` und drückt Enter, und die nächste Zeile fängt
wieder mit `- ` an. Genauso bei `[ ] `. Das ist alles, was es an
„Formatierung" gibt, und es fühlt sich wie Papier an, weil man
nie den Stift wechselt.
---------------------------------------------------------------------
WAS HIER NICHT PASSIERT
Es gibt keine Rolle, die mehr sieht. Kein „Leitung"-Zweig, kein
Umschalter, keine fremde Liste. Wer das hier liest und eine
Sichtbarkeitsregel sucht: Es gibt keine, und das ist der Sinn der
Sache (siehe notiz-tabellen.js).
===================================================================== */
(function () {
'use strict';
const $ = (id) => document.getElementById(id);
const el = (was, klasse, text) => {
const e = document.createElement(was);
if (klasse) e.className = klasse;
if (text !== undefined) e.textContent = text;
return e;
};
/* ---------- Zustand ---------------------------------------------- */
let notizen = [];
let farben = [];
let aktuell = null; // die offene Notiz (Objekt aus `notizen`)
let suchwort = '';
let papierkorbZahl = 0;
let textMax = 20000;
let korbTage = 30;
const feld = $('feld');
const deck = $('deck');
/* ---------- Fetch-Hülle ------------------------------------------- */
async function hole(pfad, einstellungen) {
const a = await fetch(pfad, { credentials: 'same-origin', ...einstellungen });
if (a.status === 401) { location.href = 'index.html'; throw new Error('weg'); }
return a;
}
async function fehlerText(a, ersatz) {
try {
const j = await a.json();
return typeof j?.fehler === 'string' && j.fehler ? j.fehler : ersatz;
} catch { return ersatz; }
}
function melde(text) {
const f = $('fehler');
f.textContent = text || '';
f.hidden = !text;
}
/* „vor 3 Minuten" statt eines Zeitstempels: Bei einer Notiz ist die
Frage „wann war ich zuletzt dran?", nicht „welcher Tag war das?".
Das genaue Datum bleibt als Titel am Element. */
function herZeit(iso) {
if (!iso) return '';
const ms = Date.now() - Date.parse(iso);
if (!Number.isFinite(ms)) return '';
const min = Math.floor(ms / 60000);
if (min < 1) return 'gerade eben';
if (min < 60) return `vor ${min} Min.`;
const std = Math.floor(min / 60);
if (std < 24) return `vor ${std} Std.`;
const tage = Math.floor(std / 24);
if (tage === 1) return 'gestern';
if (tage < 30) return `vor ${tage} Tagen`;
return new Date(iso).toLocaleDateString('de-DE',
{ day: '2-digit', month: '2-digit', year: 'numeric' });
}
const genau = (iso) => (iso
? new Date(iso).toLocaleString('de-DE', { dateStyle: 'full', timeStyle: 'short' })
: '');
/* =====================================================================
DIE DECKSCHICHT
Sie zeichnet denselben Text noch einmal — mit Haken, Überschriften
und Strichen. Die eiserne Regel dabei steht im Kopf von
notizen.css: KEINE Auszeichnung darf die Breite eines Zeichens
ändern. Deshalb gibt es hier kein `<b>`, kein `<h1>` und keine
Zeichen, die hinzukommen oder wegfallen — jedes Zeichen des Textes
landet genau einmal in der Ausgabe.
GEBAUT WIRD MIT createElement UND textContent, nicht mit einer
Zeichenkette und innerHTML. Ein Notizblock ist der Ort, an dem
jemand irgendwann `<img onerror=...>` hineinschreibt — nicht aus
Bosheit, sondern weil er sich etwas notiert. Mit textContent ist
das ein Text und bleibt einer.
===================================================================== */
/** Wandelt eine Zeile in Knoten um und hängt sie an `ziel`. */
function zeileZeichnen(ziel, zeile, treffer) {
/* Die Zeile wird von links nach rechts abgearbeitet; `rest` ist,
was noch nicht gezeichnet wurde. */
let rest = zeile;
let erledigt = false;
const stueck = (text, klasse, aufbau) => {
if (!text) return null;
const s = el('span', klasse, text);
if (aufbau) aufbau(s);
ziel.append(s);
return s;
};
/* 1. Der Zeilenanfang: Überschrift, Aufzählung, Zitat, Haken. */
let m;
if ((m = /^(\s*)(#{1,3}\s)/.exec(rest))) {
stueck(m[1], null);
stueck(m[2], 'n-marke');
const inhalt = rest.slice(m[0].length);
textMitLinks(ziel, inhalt, treffer, 'n-h');
return;
}
if ((m = /^(\s*)(\[[ xX]\])(\s?)/.exec(rest))) {
stueck(m[1], null);
const an = m[2][1] !== ' ';
erledigt = an;
stueck(m[2], 'n-hk', (s) => {
s.dataset.an = an ? 'ja' : 'nein';
s.setAttribute('role', 'checkbox');
s.setAttribute('aria-checked', an ? 'true' : 'false');
s.title = an ? 'Erledigt – klicken zum Aufheben' : 'Als erledigt abhaken';
});
stueck(m[3], null);
textMitLinks(ziel, rest.slice(m[0].length), treffer, erledigt ? 'n-erledigt' : null);
return;
}
if ((m = /^(\s*)([-*]\s)/.exec(rest))) {
stueck(m[1], null);
stueck(m[2], 'n-punkt');
textMitLinks(ziel, rest.slice(m[0].length), treffer, null);
return;
}
if ((m = /^(\s*)(>\s?)/.exec(rest))) {
stueck(m[1], null);
stueck(m[2], 'n-zitat');
textMitLinks(ziel, rest.slice(m[0].length), treffer, 'n-zitat');
return;
}
textMitLinks(ziel, rest, treffer, null);
}
/* Adressen werden anklickbar. Das Muster ist absichtlich eng: Es
trifft nur, was mit http(s):// anfängt. Ein „www." ohne Schema
wäre eine Vermutung, und eine Notiz ist kein Ort für Vermutungen. */
const ADRESSE = /https?:\/\/[^\s<>"')\]]+/g;
function textMitLinks(ziel, text, treffer, klasse) {
const stuecke = [];
let zuletzt = 0;
ADRESSE.lastIndex = 0;
let m;
while ((m = ADRESSE.exec(text))) {
if (m.index > zuletzt) stuecke.push({ t: text.slice(zuletzt, m.index), link: false });
stuecke.push({ t: m[0], link: true });
zuletzt = m.index + m[0].length;
}
if (zuletzt < text.length) stuecke.push({ t: text.slice(zuletzt), link: false });
if (!stuecke.length) stuecke.push({ t: '', link: false });
for (const s of stuecke) {
const klassen = [klasse, s.link ? 'n-link' : null].filter(Boolean).join(' ');
if (s.link) {
const a = el('a', klassen, s.t);
a.href = s.t;
a.target = '_blank';
/* `noopener` ist Pflicht: Ohne ihn bekommt die geöffnete Seite
über `window.opener` Zugriff auf diese hier. */
a.rel = 'noopener noreferrer';
ziel.append(a);
} else if (treffer) {
trefferZeichnen(ziel, s.t, treffer, klassen);
} else if (klassen) {
ziel.append(el('span', klassen, s.t));
} else {
ziel.append(document.createTextNode(s.t));
}
}
}
/** Hebt die Fundstellen der Suche hervor — mit Fläche, nicht mit Breite. */
function trefferZeichnen(ziel, text, wort, klassen) {
const klein = text.toLowerCase();
let von = 0, ab = 0;
while ((von = klein.indexOf(wort, ab)) !== -1) {
if (von > ab) ziel.append(el('span', klassen, text.slice(ab, von)));
const t = el('span', (klassen ? klassen + ' ' : '') + 'n-treffer',
text.slice(von, von + wort.length));
ziel.append(t);
ab = von + wort.length;
}
if (ab < text.length) ziel.append(el('span', klassen, text.slice(ab)));
}
function deckZeichnen() {
const text = feld.value;
deck.textContent = '';
const treffer = suchwort.length >= 2 ? suchwort : '';
const zeilen = text.split('\n');
zeilen.forEach((z, i) => {
zeileZeichnen(deck, z, treffer);
/* Der Zeilenumbruch gehört zum Text und muss mitgezeichnet
werden -- sonst stünde alles in einer Zeile. Beim LETZTEN
Element darf er nicht dazu: Ein Schreibfeld hat nach der
letzten Zeile keinen Umbruch, und die Deckschicht wäre eine
Zeile zu hoch. */
if (i < zeilen.length - 1) deck.append(document.createTextNode('\n'));
});
/* Ein Schreibfeld, das auf einen Umbruch endet, zeigt darunter
noch eine leere Zeile. Ein <div> tut das nicht -- deshalb eine
Leerzeile anhängen, damit beide gleich hoch bleiben. */
if (text.endsWith('\n')) deck.append(document.createTextNode(' '));
hoeheAnpassen();
titelSetzen();
hakenStandSetzen();
}
/** Das Schreibfeld wächst mit dem Text — gescrollt wird der Rahmen.
*
* So bleibt die Deckschicht ohne eigenes Scrollen deckungsgleich:
* Beide stehen im selben scrollenden Kasten. Ein eigener
* Bildlauf im Schreibfeld würde außerdem eine Bildlaufleiste
* einblenden, die Zeilen schmaler macht — und ab da bräche der
* Text in beiden Schichten an verschiedenen Stellen um. */
function hoeheAnpassen() {
feld.style.height = 'auto';
const hoch = Math.max(feld.scrollHeight, 320);
feld.style.height = hoch + 'px';
deck.style.minHeight = hoch + 'px';
}
function titelSetzen() {
const t = (feld.value.split('\n').find((z) => z.trim().length) || '')
.replace(/^\s*#{1,3}\s*/, '')
.replace(/^\s*[-*]\s+/, '')
.replace(/^\s*\[[ xX]\]\s*/, '')
.trim();
const k = $('blatt-titel');
k.textContent = t || 'Ohne Titel';
k.dataset.leer = t ? 'nein' : 'ja';
k.title = t || '';
}
/** Wie viele Haken gesetzt sind. Erscheint nur, wenn es welche gibt. */
function hakenZaehlen(text) {
const alle = String(text || '').match(/^\s*\[[ xX]\]/gm) || [];
const an = alle.filter((s) => !/\[ \]/.test(s)).length;
return { alle: alle.length, an };
}
function hakenStandSetzen() {
const s = $('haken-stand');
const z = hakenZaehlen(feld.value);
if (!z.alle) { s.hidden = true; return; }
s.hidden = false;
s.textContent = `${z.an} von ${z.alle} erledigt`;
s.dataset.fertig = z.an === z.alle ? 'ja' : 'nein';
}
/* =====================================================================
SICHERN
Gespeichert wird 800 ms nach dem letzten Tastendruck. Klappt das
nicht, bleibt der Text hier liegen und wird beim nächsten Versuch
nachgereicht — und der Fuß sagt es. Ein „konnte nicht speichern"
ohne Rettung wäre die schlechteste aller Meldungen: Sie erzählt
von einem Verlust, den man gerade nicht verhindern kann.
DER ZWISCHENSPEICHER IM BROWSER ist die zweite Sicherung, für den
Fall, dass jemand den Reiter schließt, bevor es durchgeht. Beim
nächsten Öffnen wird er angeboten — nicht heimlich eingespielt:
Wer zwischendurch auf einem anderen Gerät geschrieben hat, würde
sonst seine neuere Fassung verlieren.
===================================================================== */
const LAGER = 'dogi-notizen-offen';
let sicherUhr = null;
let laeuft = false;
let nochmal = false;
function standSetzen(stand, wort) {
const s = $('stand');
s.dataset.stand = stand;
s.textContent = wort;
}
function lagerSchreiben(id, text) {
try {
localStorage.setItem(LAGER, JSON.stringify({ id, text, am: Date.now() }));
} catch { /* Voller Speicher ist kein Grund, das Tippen zu stoppen. */ }
}
function lagerLeeren() {
try { localStorage.removeItem(LAGER); } catch { /* egal */ }
}
function lagerLesen() {
try { return JSON.parse(localStorage.getItem(LAGER) || 'null'); } catch { return null; }
}
function aendernGeplant() {
if (!aktuell) return;
lagerSchreiben(aktuell.id, feld.value);
standSetzen('sichert', 'sichert …');
clearTimeout(sicherUhr);
sicherUhr = setTimeout(sichern, 800);
}
async function sichern() {
if (!aktuell) return;
if (laeuft) { nochmal = true; return; }
laeuft = true;
const id = aktuell.id;
const text = feld.value;
try {
const a = await hole(`/workspace/api/notizblock/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text }),
});
if (!a.ok) {
standSetzen('offen', await fehlerText(a, 'noch nicht gesichert'));
return;
}
const j = await a.json();
const n = notizen.find((x) => x.id === id);
if (n) { n.text = text; n.geaendert = j.geaendert; n.titel = titelAus(text); }
lagerLeeren();
standSetzen('gesichert', 'gesichert');
wannSetzen();
stapelZeichnen();
} catch {
/* Kein Netz. Der Text liegt im Browser; beim nächsten Tippen
oder beim nächsten Öffnen geht es weiter. */
standSetzen('offen', 'noch nicht gesichert – bleibt hier, bis es geht');
} finally {
laeuft = false;
if (nochmal) { nochmal = false; setTimeout(sichern, 200); }
}
}
const titelAus = (text) => (String(text || '').split('\n').find((z) => z.trim().length) || '')
.replace(/^\s*#{1,3}\s*/, '').replace(/^\s*[-*]\s+/, '')
.replace(/^\s*\[[ xX]\]\s*/, '').trim().slice(0, 120);
function wannSetzen() {
const w = $('wann');
if (!aktuell) { w.textContent = ''; return; }
w.textContent = herZeit(aktuell.geaendert);
w.title = genau(aktuell.geaendert);
}
/* =====================================================================
DER STAPEL
===================================================================== */
function passt(n) {
if (suchwort.length < 2) return true;
return String(n.text || '').toLowerCase().includes(suchwort);
}
function stapelZeichnen() {
const liste = $('liste');
liste.textContent = '';
const sichtbar = notizen.filter(passt);
$('anzahl').textContent = suchwort.length >= 2
? `${sichtbar.length}/${notizen.length}` : String(notizen.length);
for (const n of sichtbar) {
const li = el('li');
const b = el('button', 'n-zettel');
b.type = 'button';
b.style.setProperty('--n-tinte', farbHex(n.farbe));
if (aktuell && n.id === aktuell.id) b.setAttribute('aria-current', 'true');
b.append(el('span', 'n-zettel__titel', n.titel || 'Ohne Titel'));
const vor = zweiteZeile(n.text);
if (vor) b.append(el('span', 'n-zettel__vor', vor));
const fuss = el('span', 'n-zettel__fuss');
if (n.angeheftet) {
const nadel = el('span', 'n-zettel__nadel', '📌');
nadel.title = 'Angeheftet';
fuss.append(nadel);
}
fuss.append(el('span', null, herZeit(n.geaendert)));
const h = hakenZaehlen(n.text);
if (h.alle) {
const hz = el('span', 'n-zettel__haken', `${h.an}/${h.alle}`);
hz.dataset.fertig = h.an === h.alle ? 'ja' : 'nein';
hz.title = `${h.an} von ${h.alle} erledigt`;
fuss.append(hz);
}
b.append(fuss);
b.addEventListener('click', () => oeffne(n.id));
li.append(b);
liste.append(li);
}
$('leer').hidden = notizen.length > 0;
$('korb-auf').hidden = papierkorbZahl === 0;
$('korb-zahl').textContent = String(papierkorbZahl);
}
function zweiteZeile(text) {
const zeilen = String(text || '').split('\n').filter((z) => z.trim().length);
return (zeilen[1] || '').replace(/^\s*\[[ xX]\]\s*/, '').replace(/^\s*[-*#>]+\s*/, '')
.trim().slice(0, 90);
}
const farbHex = (name) => (farben.find((f) => f.name === name) || farben[0] || {}).hex || '#9cde90';
/* =====================================================================
EIN BLATT AUFSCHLAGEN
===================================================================== */
function oeffne(id, { fokus = true } = {}) {
const n = notizen.find((x) => x.id === id);
if (!n) return;
/* Was im alten Blatt noch offen ist, geht zuerst raus. Sonst
landet der letzte Satz beim nächsten Blatt -- oder nirgends. */
if (aktuell && aktuell.id !== id) { clearTimeout(sicherUhr); sichern(); }
aktuell = n;
$('korb').hidden = true;
$('blatt').hidden = false;
$('blatt').style.setProperty('--n-tinte', farbHex(n.farbe));
feld.value = n.text || '';
feld.maxLength = textMax;
deckZeichnen();
wannSetzen();
standSetzen('gesichert', 'gesichert');
$('anheften').setAttribute('aria-pressed', n.angeheftet ? 'true' : 'false');
farbenZeichnen();
stapelZeichnen();
if (fokus) {
feld.focus();
/* Ans Ende springen, nicht an den Anfang: Wer ein Blatt
aufschlägt, schreibt weiter -- er liest es nicht von vorn. */
feld.setSelectionRange(feld.value.length, feld.value.length);
}
zurueckKnopf(true);
}
function schliessen() {
clearTimeout(sicherUhr);
if (aktuell) sichern();
aktuell = null;
$('blatt').hidden = true;
$('korb').hidden = true;
stapelZeichnen();
zurueckKnopf(false);
}
/* Der Zurück-Knopf der Kopfleiste schließt auf dem Handy das Blatt.
Auf dem Rechner wird er nicht gebraucht -- dort stehen Stapel und
Blatt nebeneinander, es gibt nichts zu schließen. */
function zurueckKnopf(an) {
const k = $('zurueck');
if (!k) return;
const schmal = window.matchMedia('(max-width: 860px)').matches;
k.hidden = !(an && schmal);
}
function farbenZeichnen() {
const kasten = $('farben');
kasten.textContent = '';
for (const f of farben) {
const b = el('button', 'n-farbe');
b.type = 'button';
b.style.setProperty('--f', f.hex);
b.title = f.wort;
b.setAttribute('aria-label', `Papierfarbe ${f.wort}`);
b.setAttribute('aria-pressed', aktuell && aktuell.farbe === f.name ? 'true' : 'false');
b.addEventListener('click', () => farbeSetzen(f.name));
kasten.append(b);
}
}
async function farbeSetzen(name) {
if (!aktuell) return;
const alt = aktuell.farbe;
aktuell.farbe = name;
$('blatt').style.setProperty('--n-tinte', farbHex(name));
farbenZeichnen();
stapelZeichnen();
try {
const a = await hole(`/workspace/api/notizblock/${aktuell.id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ farbe: name }),
});
if (!a.ok) throw new Error('nein');
} catch {
/* Zurückdrehen. Eine Farbe, die nach dem Neuladen wieder
umspringt, ist schlimmer als eine, die gar nicht erst
umspringt. */
aktuell.farbe = alt;
$('blatt').style.setProperty('--n-tinte', farbHex(alt));
farbenZeichnen(); stapelZeichnen();
melde('Die Farbe ließ sich nicht sichern. Versuch es gleich noch einmal.');
}
}
/* =====================================================================
DIE TASTATUR — sie führt die Liste fort
===================================================================== */
/** Setzt Text und Cursor in einem Zug, so dass der Browser es
* rückgängig machen kann.
*
* `document.execCommand('insertText')` ist offiziell veraltet und
* trotzdem das Einzige, was den Rückgängig-Stapel des Schreibfelds
* füllt. Wer stattdessen `value` setzt, nimmt dem Benutzer Strg+Z
* weg -- in einem Notizblock die unfreundlichste aller Ersparnisse.
* Geht es nicht, wird `value` gesetzt; dann fehlt nur das
* Rückgängigmachen für diesen einen Schritt. */
function einfuegen(text) {
feld.focus();
let ging = false;
try { ging = document.execCommand('insertText', false, text); } catch { ging = false; }
if (!ging) {
const a = feld.selectionStart, b = feld.selectionEnd;
feld.value = feld.value.slice(0, a) + text + feld.value.slice(b);
feld.setSelectionRange(a + text.length, a + text.length);
}
deckZeichnen();
aendernGeplant();
}
function zeileUm(pos) {
const v = feld.value;
const von = v.lastIndexOf('\n', pos - 1) + 1;
let bis = v.indexOf('\n', pos);
if (bis === -1) bis = v.length;
return { von, bis, text: v.slice(von, bis) };
}
/** Haken auf der Zeile des Cursors umschalten — oder einen setzen. */
function hakenUmschalten() {
const z = zeileUm(feld.selectionStart);
const m = /^(\s*)\[([ xX])\]/.exec(z.text);
if (m) {
const neu = m[2] === ' ' ? 'x' : ' ';
feld.setSelectionRange(z.von + m[1].length + 1, z.von + m[1].length + 2);
einfuegen(neu);
feld.setSelectionRange(z.bis, z.bis);
return;
}
/* Keiner da: einen setzen, und zwar hinter der Einrückung. */
const e = /^(\s*)/.exec(z.text)[1];
feld.setSelectionRange(z.von + e.length, z.von + e.length);
einfuegen('[ ] ');
}
feld.addEventListener('keydown', (e) => {
/* Strg/Cmd + Enter: neues Blatt. */
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault(); neuesBlatt(); return;
}
/* Strg/Cmd + Leertaste: Haken auf dieser Zeile. */
if ((e.ctrlKey || e.metaKey) && (e.key === ' ' || e.code === 'Space')) {
e.preventDefault(); hakenUmschalten(); return;
}
/* Tabulator rückt ein, statt aus dem Feld zu springen.
MIT UMSCHALT springt er weiter -- sonst käme man mit der
Tastatur nicht mehr aus dem Blatt heraus, und das wäre eine
Falle für alle, die nicht mit der Maus arbeiten. */
if (e.key === 'Tab' && !e.shiftKey) {
e.preventDefault(); einfuegen(' '); return;
}
if (e.key !== 'Enter' || e.shiftKey || e.ctrlKey || e.metaKey || e.altKey) return;
/* Enter setzt die Liste fort. */
const z = zeileUm(feld.selectionStart);
const vorn = /^(\s*)(\[[ xX]\]\s|[-*]\s|>\s?)/.exec(z.text);
if (!vorn) return;
const nurZeichen = z.text.trim() === vorn[2].trim();
e.preventDefault();
if (nurZeichen) {
/* Zweimal Enter beendet die Liste -- wie in jedem Programm, das
Listen kann. Die angefangene Zeile wird dabei geleert. */
feld.setSelectionRange(z.von, z.bis);
einfuegen('\n');
return;
}
/* Ein gesetzter Haken wird NICHT mitgenommen: Die nächste Aufgabe
ist noch nicht erledigt. */
const fortsetzung = vorn[2].replace(/\[[xX]\]/, '[ ]');
einfuegen('\n' + vorn[1] + fortsetzung);
});
feld.addEventListener('input', () => { deckZeichnen(); aendernGeplant(); });
feld.addEventListener('scroll', () => { $('papier').scrollTop = feld.scrollTop; });
/* Beim Verlassen sofort sichern statt die 800 ms abzuwarten. */
feld.addEventListener('blur', () => { clearTimeout(sicherUhr); sichern(); });
/* Ein Klick auf ein Kästchen schaltet es um. Die Deckschicht lässt
sonst alles durch; nur die Haken fangen den Klick ab. */
deck.addEventListener('click', (e) => {
const hk = e.target.closest('.n-hk');
if (!hk) return;
e.preventDefault();
/* Wo im Text steht dieses Kästchen? Gezählt wird über die Knoten
davor -- das ist genauer als eine Suche nach dem n-ten „[ ]",
weil derselbe Text mehrfach vorkommen kann. */
let pos = 0;
const gehen = (knoten) => {
for (const k of knoten.childNodes) {
if (k === hk) return true;
if (k.nodeType === 3) pos += k.nodeValue.length;
else if (gehen(k)) return true;
}
return false;
};
if (!gehen(deck)) return;
const an = hk.dataset.an === 'ja';
feld.focus();
feld.setSelectionRange(pos + 1, pos + 2);
einfuegen(an ? ' ' : 'x');
feld.setSelectionRange(pos + 3, pos + 3);
});
/* =====================================================================
DIE GRIFFE
===================================================================== */
async function neuesBlatt() {
melde('');
try {
const a = await hole('/workspace/api/notizblock', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: '' }),
});
if (!a.ok) { melde(await fehlerText(a, 'Das Blatt ließ sich nicht anlegen.')); return; }
const j = await a.json();
notizen.unshift(j.notiz);
oeffne(j.notiz.id);
} catch {
melde('Keine Verbindung. Sobald sie wieder da ist, geht es weiter.');
}
}
$('neu').addEventListener('click', neuesBlatt);
$('leer-neu').addEventListener('click', neuesBlatt);
$('anheften').addEventListener('click', async () => {
if (!aktuell) return;
const neu = !aktuell.angeheftet;
aktuell.angeheftet = neu;
$('anheften').setAttribute('aria-pressed', neu ? 'true' : 'false');
/* Umsortieren, damit man sieht, was der Knopf bewirkt hat. */
notizen.sort((a, b) => (Number(b.angeheftet) - Number(a.angeheftet))
|| (Date.parse(b.geaendert) - Date.parse(a.geaendert)));
stapelZeichnen();
try {
const a = await hole(`/workspace/api/notizblock/${aktuell.id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ angeheftet: neu }),
});
if (!a.ok) throw new Error('nein');
} catch {
aktuell.angeheftet = !neu;
$('anheften').setAttribute('aria-pressed', !neu ? 'true' : 'false');
stapelZeichnen();
melde('Das Anheften ließ sich nicht sichern.');
}
});
$('wegtun').addEventListener('click', async () => {
if (!aktuell) return;
const id = aktuell.id;
clearTimeout(sicherUhr);
await sichern();
try {
const a = await hole(`/workspace/api/notizblock/${id}`, { method: 'DELETE' });
if (!a.ok) { melde(await fehlerText(a, 'Ging nicht.')); return; }
notizen = notizen.filter((x) => x.id !== id);
papierkorbZahl += 1;
aktuell = null;
$('blatt').hidden = true;
stapelZeichnen();
zurueckKnopf(false);
/* KEINE RÜCKFRAGE VORHER, ABER EIN WEG ZURÜCK NACHHER. Eine
Rückfrage bei jedem Wegtun ist eine Warnung, die immer kommt
-- die klickt man weg. Der Papierkorb ist die bessere
Sicherung: Er kostet nichts und hält dreißig Tage. */
melde(`Weggelegt. Liegt ${korbTage} Tage im Papierkorb.`);
setTimeout(() => melde(''), 6000);
} catch {
melde('Keine Verbindung.');
}
});
/* =====================================================================
DER PAPIERKORB
===================================================================== */
$('korb-auf').addEventListener('click', korbZeigen);
$('korb-zu').addEventListener('click', () => {
$('korb').hidden = true;
if (aktuell) $('blatt').hidden = false; else zurueckKnopf(false);
});
async function korbZeigen() {
melde('');
try {
const a = await hole('/workspace/api/notizblock/papierkorb');
if (!a.ok) { melde(await fehlerText(a, 'Der Papierkorb ließ sich nicht öffnen.')); return; }
const j = await a.json();
korbTage = j.papierkorb_tage || korbTage;
$('blatt').hidden = true;
$('korb').hidden = false;
zurueckKnopf(true);
$('korb-satz').textContent = j.notizen.length
? `Hier bleibt alles ${korbTage} Tage liegen und geht dann von selbst.`
: 'Nichts drin.';
const liste = $('korb-liste');
liste.textContent = '';
for (const n of j.notizen) {
const li = el('li');
const zeile = el('div', 'n-korb-zeile');
zeile.style.setProperty('--n-tinte', farbHex(n.farbe));
const t = el('div', 'n-korb-zeile__text');
t.append(el('span', 'n-korb-zeile__titel', n.titel || 'Ohne Titel'));
const w = el('span', 'n-korb-zeile__wann',
`weggelegt ${herZeit(n.weg_am)} · verschwindet ${herTag(n.endgueltig_am)}`);
w.title = genau(n.endgueltig_am);
t.append(w);
zeile.append(t);
const zurueck = el('button', 'knopf-still', 'Zurückholen');
zurueck.type = 'button';
zurueck.addEventListener('click', () => zurueckholen(n.id));
zeile.append(zurueck);
const weg = el('button', 'n-griff n-griff--weg');
weg.type = 'button';
weg.append(el('span', 'n-griff__text', 'Endgültig'));
weg.addEventListener('click', () => endgueltig(n.id, n.titel));
zeile.append(weg);
li.append(zeile);
liste.append(li);
}
} catch {
melde('Keine Verbindung.');
}
}
/** „in 12 Tagen" — dieselbe Idee wie herZeit, nur nach vorn. */
function herTag(iso) {
const ms = Date.parse(iso) - Date.now();
if (!Number.isFinite(ms)) return '';
const tage = Math.ceil(ms / 86400000);
if (tage <= 0) return 'demnächst';
if (tage === 1) return 'morgen';
return `in ${tage} Tagen`;
}
async function zurueckholen(id) {
try {
const a = await hole(`/workspace/api/notizblock/${id}/zurueck`, { method: 'POST' });
if (!a.ok) { melde(await fehlerText(a, 'Ging nicht.')); return; }
const j = await a.json();
notizen.unshift(j.notiz);
notizen.sort((x, y) => (Number(y.angeheftet) - Number(x.angeheftet))
|| (Date.parse(y.geaendert) - Date.parse(x.geaendert)));
papierkorbZahl = Math.max(0, papierkorbZahl - 1);
$('korb').hidden = true;
oeffne(j.notiz.id);
} catch { melde('Keine Verbindung.'); }
}
async function endgueltig(id, titel) {
/* HIER WIRD GEFRAGT, und nur hier. Der Unterschied zum Wegtun ist
genau der, der eine Rückfrage rechtfertigt: Danach gibt es
keinen Weg zurück mehr. */
const name = titel ? `„${titel}"` : 'dieses Blatt';
if (!window.confirm(`${name} endgültig löschen?\n\nDanach ist es weg – `
+ 'auch für dich.')) return;
try {
const a = await hole(`/workspace/api/notizblock/${id}/endgueltig`, { method: 'DELETE' });
if (!a.ok) { melde(await fehlerText(a, 'Ging nicht.')); return; }
papierkorbZahl = Math.max(0, papierkorbZahl - 1);
stapelZeichnen();
korbZeigen();
} catch { melde('Keine Verbindung.'); }
}
/* =====================================================================
SUCHE
===================================================================== */
$('suche').addEventListener('input', (e) => {
suchwort = String(e.target.value || '').trim().toLowerCase();
stapelZeichnen();
if (aktuell) deckZeichnen();
});
$('suche').addEventListener('keydown', (e) => {
if (e.key !== 'Escape') return;
e.target.value = ''; suchwort = '';
stapelZeichnen(); if (aktuell) deckZeichnen();
});
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && (e.key === 'k' || e.key === 'K')) {
e.preventDefault(); $('suche').focus(); $('suche').select();
}
if (e.key === 'Escape' && document.activeElement === feld) {
feld.blur();
if (window.matchMedia('(max-width: 860px)').matches) schliessen();
}
});
$('zurueck')?.addEventListener('click', (e) => {
if ($('blatt').hidden && $('korb').hidden) return; // nicht unseres
e.preventDefault(); e.stopPropagation();
schliessen();
}, true);
window.addEventListener('resize', () => { zurueckKnopf(!!aktuell); hoeheAnpassen(); });
/* Beim Verlassen der Seite noch einmal losschicken. `keepalive`
sorgt dafür, dass die Anfrage den Seitenwechsel überlebt --
ohne das wird sie vom Browser abgebrochen, und der letzte Satz
wäre weg. */
window.addEventListener('pagehide', () => {
if (!aktuell) return;
const n = notizen.find((x) => x.id === aktuell.id);
if (n && n.text === feld.value) return;
lagerSchreiben(aktuell.id, feld.value);
try {
fetch(`/workspace/api/notizblock/${aktuell.id}`, {
method: 'PATCH', credentials: 'same-origin', keepalive: true,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: feld.value }),
});
} catch { /* Dann hilft der Zwischenspeicher beim nächsten Mal. */ }
});
/* =====================================================================
LOS
===================================================================== */
async function laden() {
try {
const a = await hole('/workspace/api/notizblock');
if (!a.ok) { melde(await fehlerText(a, 'Der Block ließ sich nicht laden.')); return; }
const j = await a.json();
notizen = j.notizen || [];
farben = j.farben || [];
papierkorbZahl = j.papierkorb || 0;
textMax = j.text_max || textMax;
korbTage = j.papierkorb_tage || korbTage;
stapelZeichnen();
/* Lag noch etwas im Browser? Anbieten, nicht einspielen. */
const rest = lagerLesen();
const dazu = rest && notizen.find((x) => x.id === rest.id);
if (dazu && rest.text !== dazu.text) {
melde('Auf diesem Gerät liegt noch eine ungesicherte Fassung von '
+ `„${dazu.titel || 'Ohne Titel'}". Öffne das Blatt – du kannst sie dann übernehmen.`);
dazu.rest = rest.text;
} else if (rest) {
lagerLeeren();
}
/* Auf dem Rechner wird das oberste Blatt gleich aufgeschlagen --
ein leerer Arbeitsplatz neben einem vollen Stapel ist eine
Aufforderung zum Suchen. Auf dem Handy nicht: Dort legt sich
das Blatt über alles, und man käme nie an die Liste. */
if (notizen.length && !window.matchMedia('(max-width: 860px)').matches) {
oeffne(notizen[0].id, { fokus: false });
}
} catch {
melde('Keine Verbindung zum Server.');
}
}
laden();
})();
+4
View File
@@ -445,6 +445,10 @@
nicht am Ton, damit die Farbwerkzeuge weiter mit Zahlen
rechnen koennen. */
if (b.silber) li.dataset.silber = 'ja';
/* PAPIER -- die Oberflaeche des Notizblocks. Dieselbe Bauart
wie Silber und Regenbogen: ein Merkmal am Element, damit
die Farbwerkzeuge weiter mit Zahlen rechnen koennen. */
if (b.papier) li.dataset.papier = 'ja';
if (b.gross) li.dataset.gross = 'ja';
/* Der Einlauf wird gestaffelt -- aber über eine Eigenschaft, die
bei "weniger Bewegung" nichts tut (siehe start.css). */