diff --git a/IDEAS.md b/IDEAS.md index b7a64e6..34397a1 100644 --- a/IDEAS.md +++ b/IDEAS.md @@ -372,6 +372,19 @@ Zu klären, wenn es soweit ist: - **In der HoneyBar?** Nur lesend, und auch das erst, wenn alles unter einer Domain liegt — schreiben kann sie von einer fremden Site aus nicht. +### Das Wappen — ✅ gebaut (02.09.2026) + +Aus dem `■` ist eine Pixelbiene geworden (Marcs Idee, 01.09.2026). Vier Anläufe: +einfarbig 12 × 12 (wirkte durch den angedeuteten Streifen wie ein Grinsen), +farbig 16 × 16 von vorn, als Kasten mit geraden Kanten, und schließlich die +**Seitenansicht** nach einem gedruckten Vorbild — braune Streifen, schwarze +Beine, Flügel mit Kerbe. + +Was dabei hängen blieb, steht im [README](README.md): SVG statt PNG wegen der +fremden CSPs, gelb statt mitgefärbt, und die Zeichenfassung fürs System-Fenster. +Eine 3/4-Ansicht (gerechneter Würfel, 2:1) liegt in der Schublade — sie ist +unter 64 px unbrauchbar, taugt aber für ein großes Startbild. + ### Eigene App-Logos — verschoben aus 1.3 (29.08.2026) **Beschlossen als Planung (25.08.2026), aber nicht als Nächstes.** Je App ein diff --git a/README.md b/README.md index 33ce2e9..d9dbc6b 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,57 @@ in beiden: Menüleiste, Palette, Einstellungen, App-Liste. **Tasten:** `M` Betriebsart · `S` Einstellungen · `?` Hilfe · `Esc` schließt Startmenü, oberstes Fenster oder Dialog — in dieser Reihenfolge. +## Das Wappen + +Links in beiden Leisten sitzt eine Pixelbiene — seitlich, mit braunen Streifen, +schwarzen Beinen und Flügeln mit Kerbe. Sie ersetzt das `■`, das dort seit dem +ersten Tag stand. + +**Sie steht an genau einer Stelle:** `tools/biene.py`, als 16 × 16 Zeichen, ein +Zeichen je Pixel. Daraus entstehen alle Fassungen: + +``` +python3 tools/biene.py +``` + +| Ausgabe | wohin | +|---|---| +| `web/img/biene.svg` | Favicon | +| `web/js/biene.js` | Menüleiste des Schreibtischs (`BIENE_SVG`), System-Fenster und Startlauf (`BIENE_ASCII`) | +| `web/img/biene-256.png`, `-32.png` | Doku und Vorschau | + +In die HoneyBar ist dieselbe SVG-Zeile **einkopiert** — sie ist eine einzelne +Datei ohne zweite Anfrage. Ein Test (`TestWappenIstUeberallDasselbe`) hält die +drei Kopien zusammen; wer das Raster ändert, lässt das Skript laufen und +kopiert die Zeile neu. + +**Warum SVG und nicht PNG?** Die HoneyBar läuft in *fremden* Apps. Ein `` +holte dort eine Datei von HoneyOS, und ob das erlaubt ist, entscheidet die CSP +der fremden App (`img-src`). Markup kann niemand blockieren, es braucht keine +zweite Anfrage, und es ist mit 2,6 kB kleiner als das PNG. + +**Warum bleibt sie gelb?** In der grünen und der eisblauen Röhre wechselt alles +die Farbe mit — die Biene nicht. Ein Zeichen, das die Farbe wechselt, ist ein +Symbol wie *wolke*; eines, das seine Farbe behält, ist ein Wappen. Der eine +warme Fleck liest sich als „das hier ist das System selbst". + +### Und aus Schriftzeichen + +Im System-Fenster und über dem Startlauf steht dieselbe Biene aus `#`, `:` und +`-`. Beides sind Textflächen — ein Bild darin wäre ein Fremdkörper, wie ein +Foto in einer Tabelle. So wird aus dem System-Fenster ein **neofetch**. + +Zwei Dinge stecken in der Umsetzung, die man nur durch Ausprobieren findet: Ein +Zeichen ist etwa halb so breit wie hoch, ein Pixel muss also **zwei Zeichen** +breit sein. Und Blockzeichen (`█ ▓ ▒`) sehen im Editor perfekt aus, hinterlassen +im Browser aber feine Fugen zwischen den Zellen — der Körper wird zur Waffel. +Gewöhnliche Schriftzeichen behaupten gar nicht erst, eine Fläche zu sein. + +Dass ausgerechnet die **Seitenansicht** als Text funktioniert, ist kein Zufall: +Ihre Streifen laufen senkrecht, also bleibt jede Textzeile ein +zusammenhängendes Stück Körper. Von vorn zerfällt die Biene in Streifen und +liest sich wie ein Strichcode. + ## Widgets Ein Widget ist eine **Beschreibung**, kein Kasten in der `index.html`: diff --git a/main.go b/main.go index ca337d2..4b7df52 100644 --- a/main.go +++ b/main.go @@ -52,7 +52,7 @@ func ladeGlyphen() map[string][]string { // Version ist die einzige Stelle, an der sie steht. build.sh liest sie hier aus // und vergibt denselben Docker-Tag; die Oberfläche holt sie über /api/public. // Vorher stand sie zusätzlich in os.js und lief mit den Image-Tags auseinander. -const Version = "1.2" +const Version = "1.3" type Server struct { cfg config.Config diff --git a/tools/biene.py b/tools/biene.py new file mode 100644 index 0000000..d1e9f25 --- /dev/null +++ b/tools/biene.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +"""Erzeugt das HoneyOS-Wappen aus einem Textraster. + +Die Biene steht **hier** und nirgends sonst: als 16 × 16 Zeichen, ein Zeichen +je Pixel. Daraus entstehen alle Fassungen, die HoneyOS braucht — SVG für die +beiden Leisten, PNG für Dokumentation und Vorschau, und eine Fassung aus +Schriftzeichen fürs System-Fenster und den Startlauf. + + python3 tools/biene.py + +Kein Pillow, keine Abhängigkeit: Ein PNG ist ein Kopfteil, ein zlib-Strom aus +Bildzeilen (jede mit einem Filterbyte davor) und eine Prüfsumme — das kann die +Standardbibliothek. Der Vorteil gegenüber einem Bildprogramm ist nicht die +Bequemlichkeit, sondern dass die **Quelle Text bleibt**: Ein Pixel ändern heißt +einen Buchstaben ändern, danach stimmen wieder alle Fassungen überein. +""" +import json +import pathlib +import struct +import zlib + +WURZEL = pathlib.Path(__file__).resolve().parent.parent + +# Die Farben nach dem Vorbild: Die Streifen sind **braun**. Schwarz sind nur +# Augen, Beine und Fühler — das ist der Unterschied zwischen warm und hart. +FARBEN = { + "Y": (242, 179, 61), # Honiggelb, der Körper + "L": (255, 208, 96), # Oberkante, eine Spur heller + "y": (210, 149, 42), # Unterseite im Schatten + "S": (92, 58, 28), # Streifen + "s": (70, 43, 20), # Streifen an der Unterkante + "E": (24, 20, 16), # Augenfeld und Beine + "B": (99, 199, 192), # der Lichtpunkt im Auge + "D": (59, 36, 18), # Fühler + "W": (245, 242, 231), # Flügel + "w": (214, 209, 190), # Flügel, untere Lage +} + +# Von der Seite: Stachel hinten, Kopf in Laufrichtung. Diese Ansicht ist es +# geworden, weil ihre Streifen **senkrecht** laufen — deshalb bleibt sie auch +# als Text lesbar, wo die Vorderansicht in Streifen zerfällt. +BIENE = [ + "....WW...WW.....", + "..WWWWW.WWWWW...", + "..WWWWWWWWWWW.D.", + "...WWWWWWWWW.DD.", + "...LLLLLLLLLLL..", + "..YYSSYYSSYYYYY.", + ".YYYSSYYSSYYEBY.", + "SYYYSSYYSSYYEEY.", + ".YYYSSYYSSYYYYY.", + "..yssyyssyyyyyy.", + "...EE..EE..EE...", + "...EE..EE..EE...", + "................", + "................", + "................", + "................", +] + + +def svg(raster): + """Waagerechte Läufe gleicher Farbe werden ein Rechteck — dieselbe Idee wie + bei den Pixelsymbolen in web/js/glyphs.js, nur mit Farbe statt currentColor. + + Warum SVG und nicht PNG für die Leisten: Die HoneyBar läuft in *fremden* + Apps. Ein holt dort eine Datei von HoneyOS, und ob das erlaubt ist, + entscheidet die CSP der fremden App. Markup kann niemand blockieren.""" + teile = [] + for y, zeile in enumerate(raster): + x = 0 + while x < len(zeile): + z = zeile[x] + if z not in FARBEN: + x += 1 + continue + lauf = 1 + while x + lauf < len(zeile) and zeile[x + lauf] == z: + lauf += 1 + r, g, b = FARBEN[z] + teile.append(f'') + x += lauf + return ('") + + +def png(raster, faktor, ziel): + """Vergrößert wird um ganze Faktoren, damit jedes Pixel ein Quadrat bleibt.""" + n = len(raster) + roh = bytearray() + for zeile in raster: + pixel = bytearray() + for zeichen in zeile: + r, g, b = FARBEN.get(zeichen, (0, 0, 0)) + pixel += bytes((r, g, b, 255 if zeichen in FARBEN else 0)) * faktor + for _ in range(faktor): + roh += b"\x00" + pixel # Filterbyte 0: keine Vorhersage + + def block(art, daten): + return (struct.pack(">I", len(daten)) + art + daten + + struct.pack(">I", zlib.crc32(art + daten) & 0xFFFFFFFF)) + + kopf = struct.pack(">IIBBBBB", n * faktor, n * faktor, 8, 6, 0, 0, 0) + ziel.write_bytes(b"\x89PNG\r\n\x1a\n" + block(b"IHDR", kopf) + + block(b"IDAT", zlib.compress(bytes(roh), 9)) + block(b"IEND", b"")) + + +# Ein Zeichen ist etwa halb so breit wie hoch — **ein Pixel sind zwei Zeichen**, +# sonst steht die Biene hochkant. Blockzeichen (█ ▓ ▒) wären naheliegend, +# kacheln aber nicht: Zwischen den Zellen bleiben Fugen, und der Körper wird zur +# Waffel. Gewöhnliche Schriftzeichen behaupten gar nicht erst, eine Fläche zu +# sein — deshalb sehen sie besser aus. +ZEICHEN = {"Y": "#", "L": "#", "y": "#", "S": ":", "s": ":", "E": ":", "D": ":", "W": "-", "B": "o"} + + +def text(raster): + zeilen = ["".join(ZEICHEN.get(c, " ") * 2 for c in z).rstrip() for z in raster] + while zeilen and not zeilen[0]: + zeilen.pop(0) + while zeilen and not zeilen[-1]: + zeilen.pop() + return "\n".join(zeilen) + + +def main(): + bild = svg(BIENE) + zeichen = text(BIENE) + + (WURZEL / "web/img/biene.svg").write_text(bild + "\n") + png(BIENE, 16, WURZEL / "web/img/biene-256.png") + png(BIENE, 2, WURZEL / "web/img/biene-32.png") + + # Für die Oberfläche als Modul: Das Wappen im Markup (die Leiste holt kein + # Bild nach) und die Zeichenfassung fürs System-Fenster. + (WURZEL / "web/js/biene.js").write_text( + "/* Erzeugt von tools/biene.py — nicht von Hand ändern.\n" + " *\n" + " * Das Wappen als Markup statt als Bilddatei: So kommt es ohne zweite\n" + " * Anfrage aus und lässt sich mit dem Text daneben ausrichten.\n" + " */\n" + # Einfache Anführungszeichen: So steht das Markup wörtlich da und ist + # mit web/img/biene.svg vergleichbar — ein Test hält beides gleich. + f"export const BIENE_SVG = '{bild}';\n\n" + f"export const BIENE_ASCII = {json.dumps(zeichen)};\n" + ) + print(text(BIENE)) + print(f"\nweb/img/biene.svg ({len(bild)} Zeichen), biene-256.png, biene-32.png, web/js/biene.js") + + +if __name__ == "__main__": + main() diff --git a/web/css/style.css b/web/css/style.css index d9e5dca..c00432b 100644 --- a/web/css/style.css +++ b/web/css/style.css @@ -202,7 +202,24 @@ body { white-space: nowrap; } .mb-logo b { font-weight: 400; color: var(--p-mid); } -.mb-logo .hex { font-size: 10px; vertical-align: 2px; } +/* Das Wappen bleibt gelb, während der Rest die Röhre wechselt: Ein Zeichen, + das die Farbe mitwechselt, ist ein Symbol wie „wolke" — eines, das seine + Farbe behält, ist ein Wappen. */ +.mb-logo .hex { display: inline-block; width: 20px; height: 20px; vertical-align: -6px; } +.mb-logo .hex svg { display: block; width: 100%; height: 100%; } + +/* Die Biene aus Schriftzeichen — im System-Fenster und über dem Startlauf. + Ein Zeichen ist halb so breit wie hoch, deshalb ist im Raster jeder Pixel + zwei Zeichen breit (siehe tools/biene.py). */ +.biene-text { + margin: 0 0 8px; + color: var(--p); + font-family: inherit; + font-size: 9px; + line-height: 1.1; + overflow-x: auto; +} +.boot-biene { font-size: 11px; margin-bottom: 14px; } .mb-apps { display: flex; diff --git a/web/honeybar/honeybar.js b/web/honeybar/honeybar.js index 0973673..6025463 100644 --- a/web/honeybar/honeybar.js +++ b/web/honeybar/honeybar.js @@ -98,6 +98,13 @@ "display:block", "color-scheme:dark", ].join(";"); + /* Das Wappen. Es steht hier als Markup, weil die HoneyBar in fremden + * Apps läuft: Ein holte eine Datei von HoneyOS, und ob das geht, + * entscheidet die CSP der fremden App. Erzeugt von tools/biene.py — + * dieselbe Zeile steht in web/img/biene.svg (ein Test hält sie gleich). + */ + const WAPPEN = ''; + const root = host.attachShadow({ mode: "open" }); root.innerHTML = ` @@ -136,10 +143,12 @@ } .home { letter-spacing: .14em; color: var(--p); padding-left: 2px; } .home b { color: var(--p-mid); font-weight: 400; } - /* Dasselbe Quadrat wie in der Menüleiste des Schreibtischs: dort ist es - klein und leicht angehoben. Ein Zeichen in voller Größe sah daneben - wie ein anderes Logo aus. */ - .home .hex { font-size: 10px; vertical-align: 2px; } + /* Dasselbe Wappen wie in der Menüleiste des Schreibtischs, in derselben + Größe. Es bleibt gelb, während der Rest die Röhre wechselt: Ein + Zeichen, das die Farbe mitwechselt, ist ein Symbol — eines, das seine + Farbe behält, ist ein Wappen. */ + .home .hex { display: inline-block; width: 20px; height: 20px; vertical-align: -6px; } + .home .hex svg { display: block; width: 100%; height: 100%; } .home:hover { border-color: var(--p-dim); } .home[aria-expanded="true"] { border-color: var(--p-dim); background: var(--p-faint); } /* Bei vielen Apps schiebt man den Streifen seitwärts. Ohne @@ -247,7 +256,7 @@
- +
diff --git a/web/img/biene-256.png b/web/img/biene-256.png new file mode 100644 index 0000000..efab561 Binary files /dev/null and b/web/img/biene-256.png differ diff --git a/web/img/biene-32.png b/web/img/biene-32.png new file mode 100644 index 0000000..1011c2f Binary files /dev/null and b/web/img/biene-32.png differ diff --git a/web/img/biene.svg b/web/img/biene.svg new file mode 100644 index 0000000..bbe19cf --- /dev/null +++ b/web/img/biene.svg @@ -0,0 +1 @@ + diff --git a/web/index.html b/web/index.html index 9868deb..ed0fad6 100644 --- a/web/index.html +++ b/web/index.html @@ -5,7 +5,7 @@ HoneyOS - + @@ -13,6 +13,7 @@
@@ -33,7 +34,7 @@
diff --git a/web/js/biene.js b/web/js/biene.js new file mode 100644 index 0000000..ffbcfd4 --- /dev/null +++ b/web/js/biene.js @@ -0,0 +1,8 @@ +/* Erzeugt von tools/biene.py — nicht von Hand ändern. + * + * Das Wappen als Markup statt als Bilddatei: So kommt es ohne zweite + * Anfrage aus und lässt sich mit dem Text daneben ausrichten. + */ +export const BIENE_SVG = ''; + +export const BIENE_ASCII = " ---- ----\n ---------- ----------\n ---------------------- ::\n ------------------ ::::\n ######################\n ####::::####::::##########\n ######::::####::::####::oo##\n::######::::####::::####::::##\n ######::::####::::##########\n ##::::####::::############\n :::: :::: ::::\n :::: :::: ::::"; diff --git a/web/js/os.js b/web/js/os.js index d8e882e..5b9e5e2 100644 --- a/web/js/os.js +++ b/web/js/os.js @@ -4,6 +4,7 @@ import { SS_BOOTED, state, } from "./core.js"; import { GLYPHS, glyphSvg } from "./glyphs.js"; +import { BIENE_ASCII, BIENE_SVG } from "./biene.js"; import { widgetPlan, widgetPlanSetzen, widgetsLaden, widgetsOrdnen, widgetsStarten, widgetsZeichnen, } from "./widgets/rahmen.js"; @@ -79,6 +80,9 @@ function runBoot() { return new Promise((resolve) => { const log = $("bootLog"); const el = $("boot"); + // Über dem Protokoll die Biene — auch hier aus Schriftzeichen, denn der + // Startlauf ist Text und nichts anderes. + $("bootBiene").textContent = BIENE_ASCII; el.hidden = false; const lines = bootLines(); let i = 0; @@ -122,6 +126,9 @@ function runBoot() { function renderShell() { $("desktop").hidden = false; + // Das Wappen steht im Markup, nicht als Bilddatei — dieselbe Zeile wie in + // der HoneyBar fremder Apps, damit beide Leisten wirklich dasselbe zeigen. + $("logoBiene").innerHTML = BIENE_SVG; renderMenubarApps(); renderMode(); tickClock(); diff --git a/web/js/widgets/system.js b/web/js/widgets/system.js index 46b88a0..8c1fb1c 100644 --- a/web/js/widgets/system.js +++ b/web/js/widgets/system.js @@ -1,4 +1,5 @@ import { escapeHtml, state } from "../core.js"; +import { BIENE_ASCII } from "../biene.js"; import { widgetAnmelden } from "./rahmen.js"; /* ── Systemzustand ───────────────────────────────────────────────────────── @@ -6,6 +7,11 @@ import { widgetAnmelden } from "./rahmen.js"; * Nur im Terminal-Modus ein Widget. Auf dem Schreibtisch gibt es dieselben * Zahlen als Fenster über das Startmenü — ein Schild, das dauerhaft „modus: * desktop" behauptet, während man im Desktop-Modus steht, sagt niemandem etwas. + * + * Die Biene steht hier **aus Schriftzeichen**, nicht als Bild: Das Fenster + * besteht aus lauter Textzeilen, und ein Bild darin wäre ein Fremdkörper — wie + * ein Foto in einer Tabelle. Als Text gehört sie dazu, und das Ganze wird zu + * dem, was man bei so einem Fenster ohnehin im Kopf hat: einem neofetch. */ export function statsHtml() { const up = Object.values(state.probes).filter((s) => s === "up").length; @@ -25,5 +31,5 @@ widgetAnmelden({ id: "system", titel: "system", wo: ["terminal"], - inhalt: statsHtml, + inhalt: () => `
${BIENE_ASCII}
${statsHtml()}`, }); diff --git a/wetter_test.go b/wetter_test.go index b67a992..d4dcf1f 100644 --- a/wetter_test.go +++ b/wetter_test.go @@ -177,3 +177,30 @@ func TestNotizNichtUeberDenSchluessel(t *testing.T) { t.Fatalf("mit Schlüssel: %d, erwartet 401", rec.Code) } } + +// Das Wappen steht an drei Stellen: als Datei (Favicon), als Modul für den +// Schreibtisch und als Zeile in der HoneyBar — die kann keine Datei nachladen, +// weil sie in fremden Apps läuft und deren CSP das entscheidet. +// +// Drei Kopien laufen auseinander, sobald jemand eine davon von Hand anfasst. +// Erzeugt werden alle von tools/biene.py; dieser Test hält sie zusammen. +func TestWappenIstUeberallDasselbe(t *testing.T) { + roh, err := os.ReadFile("web/img/biene.svg") + if err != nil { + t.Fatalf("web/img/biene.svg fehlt: %v", err) + } + wappen := strings.TrimSpace(string(roh)) + if !strings.HasPrefix(wappen, "