"""Darstellungs-Metadaten zu den Seltenheitsstufen. Bewusst hier dupliziert statt aus ``detector.species_de`` importiert: die Web-App soll ohne numpy und LiteRT laufen können, damit der Container klein bleibt und sich unabhängig vom Detector deployen lässt. """ # Regionen. Nur "de" ist mit Daten gefüllt — die Struktur steht, damit eine # zweite Region später kein Umbau ist, sondern ein Datensatz. REGIONS = { "de": ("Deutschland", "Germany"), } # Lebensraum: die Achse, nach der sich 148 Arten sinnvoll teilen lassen. # Ohne sie ersäuft die Gartensammlung in Wasservögeln. HABITATS = { "garten": ("Garten & Siedlung", "Gardens & settlements"), "wald": ("Wald", "Woodland"), "offenland": ("Feld & Wiese", "Fields & meadows"), "wasser": ("Wasser & Ufer", "Water & shoreline"), "gebirge": ("Gebirge", "Mountains"), } RARITY_COLOR = { "haeufig": "#7f9b6a", "gelegentlich": "#4f8fbf", "selten": "#9b7ef0", "episch": "#e0851f", "legendaer": "#d4af37", } # Typ -> (Anzeigename, Fläche, Schrift). Gespiegelt aus # detector/species_info.py, damit die Web-App ohne den Detector auskommt. TYPE_STYLE = { "meise": ("Meise", "#f2c744", "#3b2f00"), "fink": ("Fink", "#e0574f", "#fff1f0"), "sperling": ("Sperling", "#b08347", "#2e1f0c"), "drossel": ("Drossel", "#7a5c3e", "#f5ece3"), "schnaepper": ("Schnäpper", "#ef8354", "#3b1a0a"), "grasmuecke": ("Grasmücke", "#8fae5d", "#1f2a10"), "specht": ("Specht", "#d64545", "#fff0f0"), "rabenvogel": ("Rabenvogel", "#8b7fd4", "#161233"), "taube": ("Taube", "#a8a5b8", "#26242e"), "schwalbe": ("Schwalbe", "#5aa9e6", "#0c2338"), "greifvogel": ("Greifvogel", "#c9963f", "#33240a"), "eule": ("Eule", "#5c6bc0", "#f0f1ff"), "klettervogel": ("Klettervogel", "#4db6ac", "#0c2b28"), "buschvogel": ("Buschvogel", "#9c7b5c", "#f6efe8"), "stelze": ("Stelze", "#cfd8dc", "#22282a"), "ammer": ("Ammer", "#e5c04a", "#332a06"), # Gruppen, die es im Garten nicht gibt — gespiegelt aus # detector/species_extra.py. Fehlen sie hier, rendert die Karte einen # Strich statt eines Typs. "schreitvogel": ("Schreitvogel", "#6b8f8a", "#f0f6f5"), "ente": ("Ente", "#3f7d9e", "#eef7fb"), "gans": ("Gans", "#8d9aa6", "#151b20"), "moewe": ("Möwe", "#7fa8c9", "#101c26"), "watvogel": ("Watvogel", "#b3966b", "#241c0e"), "taucher": ("Taucher", "#4a7a8c", "#eef6f9"), "ralle": ("Ralle", "#5f6b5a", "#eef2ec"), "lerche": ("Lerche", "#c2a878", "#241d0d"), "wuerger": ("Würger", "#9a6f8f", "#f7eef5"), "huhn": ("Hühnervogel", "#a4703f", "#f8efe6"), "exot": ("Exot", "#22a3a3", "#06302f"), } ZUG_LABEL = { "stand": "Standvogel", "teil": "Teilzieher", "zug": "Zugvogel", } ZUG_LABEL_EN = { "stand": "Resident", "teil": "Partial migrant", "zug": "Migrant", } # Typnamen auf Englisch. Bewusst die gängigen Gruppenbegriffe, nicht die # wörtliche Übertragung — „Schnäpper" heißt hier Chat, „Buschvogel" Skulker. TYPE_LABEL_EN = { "meise": "Tit", "fink": "Finch", "sperling": "Sparrow", "drossel": "Thrush", "schnaepper": "Chat", "grasmuecke": "Warbler", "specht": "Woodpecker", "rabenvogel": "Corvid", "taube": "Pigeon", "schwalbe": "Swallow", "greifvogel": "Raptor", "eule": "Owl", "klettervogel": "Climber", "buschvogel": "Skulker", "stelze": "Wagtail", "ammer": "Bunting", "schreitvogel": "Long-legged", "ente": "Duck", "gans": "Goose", "moewe": "Gull", "watvogel": "Shorebird", "taucher": "Grebe", "ralle": "Rail", "lerche": "Lark", "wuerger": "Shrike", "huhn": "Gamebird", "exot": "Exotic", } RARITY_LABEL_EN = { "haeufig": "Common", "gelegentlich": "Occasional", "selten": "Rare", "episch": "Epic", "legendaer": "Legendary", } def _hex(c: str) -> tuple[int, int, int]: c = c.lstrip("#") return int(c[0:2], 16), int(c[2:4], 16), int(c[4:6], 16) def mix(color: str, other: str, ratio: float) -> str: """Mischt zwei Hex-Farben. Ersatz für CSS ``color-mix()``. ``color-mix()`` gibt es erst ab Safari 16.2 — auf älteren Geräten fällt jede Regel, die es benutzt, ersatzlos weg, und die Seite bricht auseinander. Vorberechnen kostet nichts und funktioniert überall. ``ratio`` ist der Anteil von ``color``. """ a, b = _hex(color), _hex(other) return "#" + "".join( f"{round(a[i] * ratio + b[i] * (1 - ratio)):02x}" for i in range(3) ) # Oberflächen, gegen die gemischt wird — müssen zu den Werten in vogelalbum.css # passen. Je Modus ein Satz. LIGHT_SURFACE_SOFT = "#f4f7f2" LIGHT_LINE = "#dfe6dd" LIGHT_INK = "#1a2a20" DARK_SURFACE_SOFT = "#1b2129" DARK_LINE = "#2f3945" DARK_PAPER = "#141920" def type_vars(typ: str) -> str: """Alle abgeleiteten Typfarben als CSS-Variablen. Landet als ``style=""``. Es werden **beide** Modi ausgegeben, mit den Endungen ``-l`` und ``-d``. Das Stylesheet entscheidet über eine Indirektion, welcher Satz gilt: :root { --typ-wash: var(--typ-wash-l); } [data-theme="dark"] { --typ-wash: var(--typ-wash-d); } Das funktioniert, weil Custom Properties erst am Verwendungsort aufgelöst werden — dort sind ``--typ-wash-l/-d`` von der Karte geerbt. Nötig ist der Umweg, weil ein Inline-Style sich nicht per Stylesheet überschreiben lässt. Die Rollen: ``--typ`` die reine Farbe für Flächen und Bänder, ``--typ-text`` für Text (auf Weiß abgedunkelt, auf Dunkel aufgehellt), ``--typ-wash`` als blasser Hintergrund, ``--typ-line`` für Rahmen. """ label, farbe, ink = TYPE_STYLE.get(typ, ("—", "#7f9b6a", "#ffffff")) return ( f"--typ:{farbe};" f"--typ-ink:{ink};" # hell f"--typ-text-l:{mix(farbe, LIGHT_INK, 0.62)};" f"--typ-wash-l:{mix(farbe, LIGHT_SURFACE_SOFT, 0.13)};" f"--typ-card-l:{mix(farbe, LIGHT_SURFACE_SOFT, 0.22)};" f"--typ-line-l:{mix(farbe, LIGHT_LINE, 0.45)};" # dunkel — dort muss die Farbe aufgehellt werden, um lesbar zu bleiben, # und die Flächen viel zurückhaltender sein, sonst blendet es. f"--typ-text-d:{mix(farbe, '#ffffff', 0.80)};" f"--typ-wash-d:{mix(farbe, DARK_SURFACE_SOFT, 0.14)};" f"--typ-card-d:{mix(farbe, DARK_PAPER, 0.20)};" f"--typ-line-d:{mix(farbe, DARK_LINE, 0.42)};" ) RARITY_GLOW = { "haeufig": "none", "gelegentlich": "none", "selten": "0 0 24px -6px #9b7ef0", "episch": "0 0 28px -4px #e0851f", "legendaer": "0 0 34px -2px #d4af37", }