| """SVG avatar sigils for entities — ornate monogram emblems per type. |
| |
| Real photographic creature art can't be matched reliably to abstract, |
| quirky entities (a "Joke Dragon" is not a stock phoenix), so portraits use |
| an elegant type-coloured sigil with the entity's monogram. This reads as a |
| deliberate heraldic mark rather than a mismatched photo. |
| """ |
|
|
| from __future__ import annotations |
|
|
| TYPE_PALETTES = { |
| "character": {"bg": "#1f3a2c", "ring": "#d4a84b", "accent": "#8fbc8f", "mark": "✦"}, |
| "creature": {"bg": "#3a281c", "ring": "#e07b4a", "accent": "#f0c878", "mark": "❧"}, |
| "object": {"bg": "#1c2c3a", "ring": "#7fa8d0", "accent": "#a8c8e8", "mark": "◈"}, |
| "place": {"bg": "#1c3a2c", "ring": "#60a060", "accent": "#90d090", "mark": "▲"}, |
| } |
|
|
| LOCATION_TINTS = { |
| "library": "#c8a040", |
| "sea": "#7090b0", |
| "clock-forest": "#b06020", |
| "moon-market": "#8060b0", |
| "valley": "#506050", |
| "crossroads": "#c04020", |
| "mirror-bogs": "#406858", |
| "hollow-mountain": "#808890", |
| } |
|
|
|
|
| def render_avatar(entity: dict, size: int = 80, location_slug: str | None = None) -> str: |
| etype = entity.get("type", "character") |
| palette = TYPE_PALETTES.get(etype, TYPE_PALETTES["character"]) |
| tint = LOCATION_TINTS.get(location_slug or "", palette["ring"]) |
| name = entity.get("display_name") or entity.get("name", "?") |
| initial = name.replace("The ", "")[0].upper() if name else "?" |
| uid = (entity.get("id") or "00000000")[:8] |
|
|
| return f""" |
| <svg class="entity-avatar" width="{size}" height="{size}" viewBox="0 0 100 100" |
| xmlns="http://www.w3.org/2000/svg"> |
| <defs> |
| <radialGradient id="av-{uid}" cx="50%" cy="38%" r="70%"> |
| <stop offset="0%" stop-color="{palette['accent']}" stop-opacity="0.32"/> |
| <stop offset="55%" stop-color="{palette['bg']}"/> |
| <stop offset="100%" stop-color="#0c1410"/> |
| </radialGradient> |
| <filter id="gl-{uid}" x="-30%" y="-30%" width="160%" height="160%"> |
| <feGaussianBlur stdDeviation="1.4" result="b"/> |
| <feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge> |
| </filter> |
| </defs> |
| <circle cx="50" cy="50" r="47" fill="url(#av-{uid})"/> |
| <circle cx="50" cy="50" r="47" fill="none" stroke="{tint}" stroke-width="2" opacity="0.9"/> |
| <circle cx="50" cy="50" r="41" fill="none" stroke="{tint}" stroke-width="0.8" opacity="0.4"/> |
| <text x="50" y="60" text-anchor="middle" fill="{palette['accent']}" |
| font-family="'Cinzel', Georgia, serif" font-size="38" font-weight="600" |
| filter="url(#gl-{uid})">{initial}</text> |
| <text x="50" y="84" text-anchor="middle" fill="{tint}" font-size="11" opacity="0.85">{palette['mark']}</text> |
| </svg> |
| """ |
|
|