Die Anmeldung bekommt die Biene in Schraegsicht

Die 3/4-Ansicht ist unter 64 px unbrauchbar und deshalb genau hier richtig:
Auf dem Anmeldebildschirm ist Platz, und man wartet ohnehin auf den Anbieter.
Sie schwebt sechs Sekunden lang zwei Pixel weit — eher ein Atmen als eine
Bewegung, und bei prefers-reduced-motion steht sie still.

Gerechnet wird sie in tools/biene.py aus demselben Raster wie die anderen
Fassungen.
This commit is contained in:
marc committed 2026-09-02 10:27:03 +02:00
1 parent 3f8b98b869
commit ff413864a9
6 files changed
+113 -3

No files matched your search

+13
View File
@@ -201,6 +201,7 @@ python3 tools/biene.py
|---|---|
| `web/img/biene.svg` | Favicon |
| `web/js/biene.js` | Menüleiste des Schreibtischs (`BIENE_SVG`), System-Fenster und Startlauf (`BIENE_ASCII`) |
| `web/img/biene-3d.svg`, `-3d-256.png` | Anmeldebildschirm — die Schrägsicht |
| `web/img/biene-256.png`, `-32.png` | Doku und Vorschau |
In die HoneyBar ist dieselbe SVG-Zeile **einkopiert** — sie ist eine einzelne
@@ -218,6 +219,18 @@ 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".
### Einmal groß: die Schrägsicht
Auf dem Anmeldebildschirm steht dieselbe Biene als **Würfel in 2:1-Schrägsicht**
— gerechnet, nicht gemalt: Das Skript legt Deckel und die zwei sichtbaren
Wangen an und malt Streifen, Augen und Flügel darauf. Die Streifen laufen
parallel zur linken Deckelkante und setzen sich auf der rechten Wange als
senkrechte Bänder fort, damit sie über den Knick hinweg zusammenpassen.
Sie ist **unter 64 px unbrauchbar** — zu viele Kanten auf zu wenig Pixeln —,
und genau deshalb steht sie dort: Der Anmeldebildschirm ist die einzige Stelle
mit Platz und Zeit. In den Leisten bleibt die Seitenansicht.
### Und aus Schriftzeichen
Im System-Fenster und über dem Startlauf steht dieselbe Biene aus `#`, `:` und
+77 -2
View File
@@ -59,6 +59,76 @@ BIENE = [
]
# ── Die 3/4-Ansicht ──────────────────────────────────────────────────────
#
# Nur für den Anmeldebildschirm: groß, einmal am Tag, mit Zeit zum Hinsehen.
# Unter 64 px zerfällt sie — zu viele Kanten auf zu wenig Pixeln.
#
# Ein Würfel hat in Pixelkunst die Steigung 2:1: zwei Pixel nach rechts, einer
# nach unten. Von Hand wird das krumm, also werden die drei sichtbaren Flächen
# gerechnet — Deckel als Raute, darunter zwei Wangen. Wichtig ist, **zeilenweise**
# zu rechnen: Wer die Raute Punkt für Punkt setzt, lässt Löcher, weil zwei
# Schritte nach rechts nur einen nach unten kosten.
def wuerfel(breite, wand):
hoehe = breite // 2
mitte = breite / 2
deckel = {
(x, y)
for y in range(hoehe)
for x in range(breite)
if abs(x + 0.5 - mitte) / 2 + abs(y + 0.5 - hoehe / 2) <= hoehe / 2
}
unterkante = {}
for (x, y) in deckel:
unterkante[x] = max(unterkante.get(x, -1), y)
flaechen = {q: "T" for q in deckel}
for x, yb in unterkante.items():
for t in range(1, wand + 1):
flaechen[(x, yb + t)] = "V" if x < mitte else "R" # vorn / rechts
return flaechen, unterkante, mitte
def schraeg(breite=22, wand=10, dx=5, dy=9, n=32, band=5):
flaechen, unterkante, mitte = wuerfel(breite, wand)
g = [["." for _ in range(n)] for _ in range(n)]
def setz(x, y, c):
if 0 <= x + dx < n and 0 <= y + dy < n:
g[y + dy][x + dx] = c
# Die Streifen laufen parallel zur linken Deckelkante (u = x - 2y) und
# setzen sich auf der rechten Wange als senkrechte Bänder fort: dieselbe
# Rechnung, ausgewertet an der Kante, an der die Wange beginnt. Deshalb
# passen sie über den Knick hinweg zusammen.
streifig = lambda x, y: ((x - 2 * y) // band) % 2 == 0
for (x, y), art in sorted(flaechen.items()):
if art == "T":
setz(x, y, "S" if streifig(x, y) else "L")
elif art == "R":
setz(x, y, "s" if streifig(x, unterkante[x]) else "y")
else:
setz(x, y, "Y")
# Augen: Rechtecke in Flächenkoordinaten — auf dem Schirm werden daraus
# Parallelogramme, genau wie beim Vorbild.
for ax in (2, 7):
for x in range(ax, ax + 3):
if x in unterkante:
for t in (3, 4, 5):
setz(x, unterkante[x] + t, "E")
if ax + 1 in unterkante:
setz(ax + 1, unterkante[ax + 1] + 4, "B")
for k in range(3): # zwei Flügel in derselben Schräge
for b in range(6):
setz(3 + 2 * k + b, -2 + k, "W")
setz(10 + 2 * k + b, -4 + k, "w")
for k in range(2): # kurze Fühler am Deckelrand
setz(int(mitte) - 5 - k, -3 - k, "D")
setz(int(mitte) + 6 + k, -5 - k, "D")
return ["".join(z) for z in g]
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.
@@ -80,7 +150,8 @@ def svg(raster):
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" '
n = len(raster)
return (f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {n} {n}" '
'shape-rendering="crispEdges" aria-hidden="true">' + "".join(teile) + "</svg>")
@@ -127,6 +198,10 @@ def main():
zeichen = text(BIENE)
(WURZEL / "web/img/biene.svg").write_text(bild + "\n")
# Für die Anmeldung: einmal groß, in Schrägsicht.
drei = schraeg()
(WURZEL / "web/img/biene-3d.svg").write_text(svg(drei) + "\n")
png(drei, 8, WURZEL / "web/img/biene-3d-256.png")
png(BIENE, 16, WURZEL / "web/img/biene-256.png")
png(BIENE, 2, WURZEL / "web/img/biene-32.png")
@@ -144,7 +219,7 @@ def main():
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")
print(f"\nweb/img/biene.svg ({len(bild)} Zeichen), biene-3d.svg, drei PNG, web/js/biene.js")
if __name__ == "__main__":
+19 -1
View File
@@ -944,8 +944,26 @@ body {
overflow: hidden;
}
/* Die Biene über dem Anmeldeknopf, in Schrägsicht. Sie schwebt ein wenig —
sechs Sekunden für zwei Pixel, also eher ein Atmen als eine Bewegung. Wer
Bewegung abgestellt hat, bekommt sie ruhig. */
.login-biene {
display: block;
width: clamp(96px, 22vw, 132px);
height: auto;
margin: 22px 0 0;
image-rendering: pixelated;
animation: schweben 6s ease-in-out infinite;
}
@keyframes schweben {
50% { transform: translateY(-4px); }
}
@media (prefers-reduced-motion: reduce) {
.login-biene { animation: none; }
}
.login-box {
margin-top: 26px;
margin-top: 20px;
display: flex;
flex-direction: column;
align-items: flex-start;
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

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

After

Width:  |  Height:  |  Size: 7.2 KiB

+3
View File
@@ -21,6 +21,9 @@
<!-- ── Anmeldung ──────────────────────────────────────────────────── -->
<div class="login" id="login" hidden>
<pre class="boot-log" id="loginLog"></pre>
<!-- Einmal groß: die Schrägsicht. Sie ist unter 64 px unbrauchbar, und
hier ist Platz — man wartet ohnehin auf den Anbieter. -->
<img class="login-biene" src="./img/biene-3d.svg" alt="" width="128" height="128" />
<div class="login-box">
<button class="login-btn" id="btnLogin">&#9656; <span id="loginLabel">Anmelden</span></button>
<p class="login-note" id="loginNote"></p>