Eine Biene als Wappen, an allen vier Stellen

Aus dem Quadrat links in beiden Leisten wird eine Pixelbiene von der Seite:
braune Streifen, schwarze Beine, Fluegel mit Kerbe. Sie steht in genau einem
Raster (tools/biene.py, 16x16 Zeichen); daraus entstehen SVG, PNG und eine
Fassung aus Schriftzeichen.

SVG statt PNG in den Leisten: Die HoneyBar laeuft in fremden Apps, und ob ein
<img> dort laedt, entscheidet deren CSP. Markup kann niemand blockieren.

Im System-Fenster und ueber dem Startlauf steht die Biene aus Schriftzeichen —
beides sind Textflaechen, ein Bild darin waere ein Fremdkoerper.

Das Wappen liegt an drei Stellen (Datei, Modul, HoneyBar-Zeile), weil die
Leiste nichts nachladen kann. TestWappenIstUeberallDasselbe haelt sie gleich.
This commit is contained in:
marc committed 2026-09-02 10:21:03 +02:00
1 parent aec042a8c9
commit 3f8b98b869
14 files changed
+301 -10

No files matched your search

+13
View File
@@ -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
+51
View File
@@ -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 `<img>`
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`:
+1 -1
View File
@@ -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
+151
View File
@@ -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 <img> 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'<rect x="{x}" y="{y}" width="{lauf}" height="1" fill="#{r:02x}{g:02x}{b:02x}"/>')
x += lauf
return ('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" '
'shape-rendering="crispEdges" aria-hidden="true">' + "".join(teile) + "</svg>")
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()
+18 -1
View File
@@ -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;
+14 -5
View File
@@ -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 <img> 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 = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" shape-rendering="crispEdges" aria-hidden="true"><rect x="4" y="0" width="2" height="1" fill="#f5f2e7"/><rect x="9" y="0" width="2" height="1" fill="#f5f2e7"/><rect x="2" y="1" width="5" height="1" fill="#f5f2e7"/><rect x="8" y="1" width="5" height="1" fill="#f5f2e7"/><rect x="2" y="2" width="11" height="1" fill="#f5f2e7"/><rect x="14" y="2" width="1" height="1" fill="#3b2412"/><rect x="3" y="3" width="9" height="1" fill="#f5f2e7"/><rect x="13" y="3" width="2" height="1" fill="#3b2412"/><rect x="3" y="4" width="11" height="1" fill="#ffd060"/><rect x="2" y="5" width="2" height="1" fill="#f2b33d"/><rect x="4" y="5" width="2" height="1" fill="#5c3a1c"/><rect x="6" y="5" width="2" height="1" fill="#f2b33d"/><rect x="8" y="5" width="2" height="1" fill="#5c3a1c"/><rect x="10" y="5" width="5" height="1" fill="#f2b33d"/><rect x="1" y="6" width="3" height="1" fill="#f2b33d"/><rect x="4" y="6" width="2" height="1" fill="#5c3a1c"/><rect x="6" y="6" width="2" height="1" fill="#f2b33d"/><rect x="8" y="6" width="2" height="1" fill="#5c3a1c"/><rect x="10" y="6" width="2" height="1" fill="#f2b33d"/><rect x="12" y="6" width="1" height="1" fill="#181410"/><rect x="13" y="6" width="1" height="1" fill="#63c7c0"/><rect x="14" y="6" width="1" height="1" fill="#f2b33d"/><rect x="0" y="7" width="1" height="1" fill="#5c3a1c"/><rect x="1" y="7" width="3" height="1" fill="#f2b33d"/><rect x="4" y="7" width="2" height="1" fill="#5c3a1c"/><rect x="6" y="7" width="2" height="1" fill="#f2b33d"/><rect x="8" y="7" width="2" height="1" fill="#5c3a1c"/><rect x="10" y="7" width="2" height="1" fill="#f2b33d"/><rect x="12" y="7" width="2" height="1" fill="#181410"/><rect x="14" y="7" width="1" height="1" fill="#f2b33d"/><rect x="1" y="8" width="3" height="1" fill="#f2b33d"/><rect x="4" y="8" width="2" height="1" fill="#5c3a1c"/><rect x="6" y="8" width="2" height="1" fill="#f2b33d"/><rect x="8" y="8" width="2" height="1" fill="#5c3a1c"/><rect x="10" y="8" width="5" height="1" fill="#f2b33d"/><rect x="2" y="9" width="1" height="1" fill="#d2952a"/><rect x="3" y="9" width="2" height="1" fill="#462b14"/><rect x="5" y="9" width="2" height="1" fill="#d2952a"/><rect x="7" y="9" width="2" height="1" fill="#462b14"/><rect x="9" y="9" width="6" height="1" fill="#d2952a"/><rect x="3" y="10" width="2" height="1" fill="#181410"/><rect x="7" y="10" width="2" height="1" fill="#181410"/><rect x="11" y="10" width="2" height="1" fill="#181410"/><rect x="3" y="11" width="2" height="1" fill="#181410"/><rect x="7" y="11" width="2" height="1" fill="#181410"/><rect x="11" y="11" width="2" height="1" fill="#181410"/></svg>';
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 @@
</style>
<div class="bar">
<button class="home" id="home" title="HoneyOS-Menü" aria-haspopup="true" aria-expanded="false"><span class="hex">&#9632;</span> HONEY<b>OS</b></button>
<button class="home" id="home" title="HoneyOS-Menü" aria-haspopup="true" aria-expanded="false"><span class="hex">${WAPPEN}</span> HONEY<b>OS</b></button>
<nav class="apps" id="apps"></nav>
<div class="right">
<span class="wx" id="wx" hidden></span>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" shape-rendering="crispEdges" aria-hidden="true"><rect x="4" y="0" width="2" height="1" fill="#f5f2e7"/><rect x="9" y="0" width="2" height="1" fill="#f5f2e7"/><rect x="2" y="1" width="5" height="1" fill="#f5f2e7"/><rect x="8" y="1" width="5" height="1" fill="#f5f2e7"/><rect x="2" y="2" width="11" height="1" fill="#f5f2e7"/><rect x="14" y="2" width="1" height="1" fill="#3b2412"/><rect x="3" y="3" width="9" height="1" fill="#f5f2e7"/><rect x="13" y="3" width="2" height="1" fill="#3b2412"/><rect x="3" y="4" width="11" height="1" fill="#ffd060"/><rect x="2" y="5" width="2" height="1" fill="#f2b33d"/><rect x="4" y="5" width="2" height="1" fill="#5c3a1c"/><rect x="6" y="5" width="2" height="1" fill="#f2b33d"/><rect x="8" y="5" width="2" height="1" fill="#5c3a1c"/><rect x="10" y="5" width="5" height="1" fill="#f2b33d"/><rect x="1" y="6" width="3" height="1" fill="#f2b33d"/><rect x="4" y="6" width="2" height="1" fill="#5c3a1c"/><rect x="6" y="6" width="2" height="1" fill="#f2b33d"/><rect x="8" y="6" width="2" height="1" fill="#5c3a1c"/><rect x="10" y="6" width="2" height="1" fill="#f2b33d"/><rect x="12" y="6" width="1" height="1" fill="#181410"/><rect x="13" y="6" width="1" height="1" fill="#63c7c0"/><rect x="14" y="6" width="1" height="1" fill="#f2b33d"/><rect x="0" y="7" width="1" height="1" fill="#5c3a1c"/><rect x="1" y="7" width="3" height="1" fill="#f2b33d"/><rect x="4" y="7" width="2" height="1" fill="#5c3a1c"/><rect x="6" y="7" width="2" height="1" fill="#f2b33d"/><rect x="8" y="7" width="2" height="1" fill="#5c3a1c"/><rect x="10" y="7" width="2" height="1" fill="#f2b33d"/><rect x="12" y="7" width="2" height="1" fill="#181410"/><rect x="14" y="7" width="1" height="1" fill="#f2b33d"/><rect x="1" y="8" width="3" height="1" fill="#f2b33d"/><rect x="4" y="8" width="2" height="1" fill="#5c3a1c"/><rect x="6" y="8" width="2" height="1" fill="#f2b33d"/><rect x="8" y="8" width="2" height="1" fill="#5c3a1c"/><rect x="10" y="8" width="5" height="1" fill="#f2b33d"/><rect x="2" y="9" width="1" height="1" fill="#d2952a"/><rect x="3" y="9" width="2" height="1" fill="#462b14"/><rect x="5" y="9" width="2" height="1" fill="#d2952a"/><rect x="7" y="9" width="2" height="1" fill="#462b14"/><rect x="9" y="9" width="6" height="1" fill="#d2952a"/><rect x="3" y="10" width="2" height="1" fill="#181410"/><rect x="7" y="10" width="2" height="1" fill="#181410"/><rect x="11" y="10" width="2" height="1" fill="#181410"/><rect x="3" y="11" width="2" height="1" fill="#181410"/><rect x="7" y="11" width="2" height="1" fill="#181410"/><rect x="11" y="11" width="2" height="1" fill="#181410"/></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

+3 -2
View File
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<meta name="theme-color" content="#0a0705" />
<title>HoneyOS</title>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' fill='%230a0705'/%3E%3Cpath d='M4 4h2v3h4V4h2v8h-2V9H6v3H4z' fill='%23ffb641'/%3E%3C/svg%3E" />
<link rel="icon" href="./img/biene.svg" />
<link rel="stylesheet" href="./css/style.css" />
</head>
<body>
@@ -13,6 +13,7 @@
<div class="crt" id="screen">
<!-- ── Boot ───────────────────────────────────────────────────────── -->
<div class="boot" id="boot" hidden>
<pre class="biene-text boot-biene" id="bootBiene" aria-hidden="true"></pre>
<pre class="boot-log" id="bootLog" aria-live="polite"></pre>
<p class="boot-hint">[ beliebige Taste = überspringen ]</p>
</div>
@@ -33,7 +34,7 @@
<!-- Das Logo ist das Systemmenü — dasselbe wie in der HoneyBar
fremder Apps, damit HoneyOS von überall gleich erreichbar ist. -->
<button class="mb-logo" id="btnSystem" title="HoneyOS-Menü" aria-haspopup="true" aria-expanded="false">
<span class="hex">&#9632;</span> HONEY<b>OS</b>
<span class="hex" id="logoBiene"></span> HONEY<b>OS</b>
</button>
<nav class="mb-apps" id="mbApps" aria-label="Anwendungen"></nav>
<div class="mb-right">
+8
View File
@@ -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 = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" shape-rendering="crispEdges" aria-hidden="true"><rect x="4" y="0" width="2" height="1" fill="#f5f2e7"/><rect x="9" y="0" width="2" height="1" fill="#f5f2e7"/><rect x="2" y="1" width="5" height="1" fill="#f5f2e7"/><rect x="8" y="1" width="5" height="1" fill="#f5f2e7"/><rect x="2" y="2" width="11" height="1" fill="#f5f2e7"/><rect x="14" y="2" width="1" height="1" fill="#3b2412"/><rect x="3" y="3" width="9" height="1" fill="#f5f2e7"/><rect x="13" y="3" width="2" height="1" fill="#3b2412"/><rect x="3" y="4" width="11" height="1" fill="#ffd060"/><rect x="2" y="5" width="2" height="1" fill="#f2b33d"/><rect x="4" y="5" width="2" height="1" fill="#5c3a1c"/><rect x="6" y="5" width="2" height="1" fill="#f2b33d"/><rect x="8" y="5" width="2" height="1" fill="#5c3a1c"/><rect x="10" y="5" width="5" height="1" fill="#f2b33d"/><rect x="1" y="6" width="3" height="1" fill="#f2b33d"/><rect x="4" y="6" width="2" height="1" fill="#5c3a1c"/><rect x="6" y="6" width="2" height="1" fill="#f2b33d"/><rect x="8" y="6" width="2" height="1" fill="#5c3a1c"/><rect x="10" y="6" width="2" height="1" fill="#f2b33d"/><rect x="12" y="6" width="1" height="1" fill="#181410"/><rect x="13" y="6" width="1" height="1" fill="#63c7c0"/><rect x="14" y="6" width="1" height="1" fill="#f2b33d"/><rect x="0" y="7" width="1" height="1" fill="#5c3a1c"/><rect x="1" y="7" width="3" height="1" fill="#f2b33d"/><rect x="4" y="7" width="2" height="1" fill="#5c3a1c"/><rect x="6" y="7" width="2" height="1" fill="#f2b33d"/><rect x="8" y="7" width="2" height="1" fill="#5c3a1c"/><rect x="10" y="7" width="2" height="1" fill="#f2b33d"/><rect x="12" y="7" width="2" height="1" fill="#181410"/><rect x="14" y="7" width="1" height="1" fill="#f2b33d"/><rect x="1" y="8" width="3" height="1" fill="#f2b33d"/><rect x="4" y="8" width="2" height="1" fill="#5c3a1c"/><rect x="6" y="8" width="2" height="1" fill="#f2b33d"/><rect x="8" y="8" width="2" height="1" fill="#5c3a1c"/><rect x="10" y="8" width="5" height="1" fill="#f2b33d"/><rect x="2" y="9" width="1" height="1" fill="#d2952a"/><rect x="3" y="9" width="2" height="1" fill="#462b14"/><rect x="5" y="9" width="2" height="1" fill="#d2952a"/><rect x="7" y="9" width="2" height="1" fill="#462b14"/><rect x="9" y="9" width="6" height="1" fill="#d2952a"/><rect x="3" y="10" width="2" height="1" fill="#181410"/><rect x="7" y="10" width="2" height="1" fill="#181410"/><rect x="11" y="10" width="2" height="1" fill="#181410"/><rect x="3" y="11" width="2" height="1" fill="#181410"/><rect x="7" y="11" width="2" height="1" fill="#181410"/><rect x="11" y="11" width="2" height="1" fill="#181410"/></svg>';
export const BIENE_ASCII = " ---- ----\n ---------- ----------\n ---------------------- ::\n ------------------ ::::\n ######################\n ####::::####::::##########\n ######::::####::::####::oo##\n::######::::####::::####::::##\n ######::::####::::##########\n ##::::####::::############\n :::: :::: ::::\n :::: :::: ::::";
+7
View File
@@ -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();
+7 -1
View File
@@ -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: () => `<pre class="biene-text">${BIENE_ASCII}</pre>${statsHtml()}`,
});
+27
View File
@@ -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, "<svg") || len(wappen) < 500 {
t.Fatalf("web/img/biene.svg sieht nicht nach dem Wappen aus (%d Zeichen)", len(wappen))
}
for _, datei := range []string{"web/honeybar/honeybar.js", "web/js/biene.js"} {
b, err := os.ReadFile(datei)
if err != nil {
t.Fatalf("%s: %v", datei, err)
}
if !strings.Contains(string(b), wappen) {
t.Errorf("%s trägt ein anderes Wappen als web/img/biene.svg — tools/biene.py laufen lassen", datei)
}
}
}