atagen

malice

public
Star0
Watch0
6d7dff75a67df5f7dc8a1843de20e5bc4db5769e
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git

Blame: web/src/lib/MxcAvatar.svelte

973f8fbdaf80atagen2026-09-28 09:10 UTC1
<script lang="ts">
973f8fbdaf80atagen2026-09-28 09:10 UTC2
  import { mediaUrl } from './api';
05a48e9e7942atagen2026-10-24 09:32 UTC3
  import { initialsOf, senderHue } from './format';
445bf199a9cfatagen2026-10-25 02:56 UTC4
  import EmojiText from './EmojiText.svelte';
973f8fbdaf80atagen2026-09-28 09:10 UTC5
973f8fbdaf80atagen2026-09-28 09:10 UTC6
  export let mxc: string | null;
973f8fbdaf80atagen2026-09-28 09:10 UTC7
  export let label: string;
973f8fbdaf80atagen2026-09-28 09:10 UTC8
  export let size = 34;
973f8fbdaf80atagen2026-09-28 09:10 UTC9
  /** 'circle' for people, 'rounded' for rooms/spaces */
973f8fbdaf80atagen2026-09-28 09:10 UTC10
  export let shape: 'circle' | 'rounded' = 'circle';
05a48e9e7942atagen2026-10-24 09:32 UTC11
  export let seed: string | null = null;
d07bca5da4aeatagen2026-10-26 03:48 UTC12
  export let eager = false;
973f8fbdaf80atagen2026-09-28 09:10 UTC13
7643577c76bbatagen2026-10-25 07:37 UTC14
  const density = typeof window === 'undefined' ? 1 : Math.min(window.devicePixelRatio || 1, 3);
7643577c76bbatagen2026-10-25 07:37 UTC15
  // rounding up to a 32px step keeps the number of cached variants per avatar small
7643577c76bbatagen2026-10-25 07:37 UTC16
  $: src = mediaUrl(mxc, Math.ceil((size * density) / 32) * 32);
973f8fbdaf80atagen2026-09-28 09:10 UTC17
973f8fbdaf80atagen2026-09-28 09:10 UTC18
  let failed = false;
973f8fbdaf80atagen2026-09-28 09:10 UTC19
  $: if (mxc) failed = false;
973f8fbdaf80atagen2026-09-28 09:10 UTC20
  $: px = `${size}px`;
d07bca5da4aeatagen2026-10-26 03:48 UTC21
  $: compact = size < 24;
d07bca5da4aeatagen2026-10-26 03:48 UTC22
  $: fontPx = `${compact ? Math.round(size * 0.56) : Math.max(10, Math.round(size * 0.36))}px`;
d07bca5da4aeatagen2026-10-26 03:48 UTC23
  $: initials = initialsOf(label || '#');
d07bca5da4aeatagen2026-10-26 03:48 UTC24
  $: shown = compact ? ([...new Intl.Segmenter().segment(initials)][0]?.segment ?? '#') : initials;
05a48e9e7942atagen2026-10-24 09:32 UTC25
  $: hue = senderHue(seed ?? label ?? '#');
973f8fbdaf80atagen2026-09-28 09:10 UTC26
</script>
973f8fbdaf80atagen2026-09-28 09:10 UTC27
973f8fbdaf80atagen2026-09-28 09:10 UTC28
{#if src && !failed}
973f8fbdaf80atagen2026-09-28 09:10 UTC29
  <img
973f8fbdaf80atagen2026-09-28 09:10 UTC30
    {src}
973f8fbdaf80atagen2026-09-28 09:10 UTC31
    alt=""
973f8fbdaf80atagen2026-09-28 09:10 UTC32
    aria-hidden="true"
973f8fbdaf80atagen2026-09-28 09:10 UTC33
    width={size}
973f8fbdaf80atagen2026-09-28 09:10 UTC34
    height={size}
d07bca5da4aeatagen2026-10-26 03:48 UTC35
    loading={eager ? 'eager' : 'lazy'}
d07bca5da4aeatagen2026-10-26 03:48 UTC36
    decoding="async"
973f8fbdaf80atagen2026-09-28 09:10 UTC37
    class="mxc-avatar"
973f8fbdaf80atagen2026-09-28 09:10 UTC38
    class:circle={shape === 'circle'}
973f8fbdaf80atagen2026-09-28 09:10 UTC39
    style={`width:${px};height:${px};`}
973f8fbdaf80atagen2026-09-28 09:10 UTC40
    on:error={() => (failed = true)}
973f8fbdaf80atagen2026-09-28 09:10 UTC41
  />
973f8fbdaf80atagen2026-09-28 09:10 UTC42
{:else}
05a48e9e7942atagen2026-10-24 09:32 UTC43
  <span class="mxc-fallback" class:circle={shape === 'circle'} aria-hidden="true" style={`width:${px};height:${px};font-size:${fontPx};background:hsl(${hue} 32% 30%);`}>
d07bca5da4aeatagen2026-10-26 03:48 UTC44
    <span><EmojiText text={shown} /></span>
973f8fbdaf80atagen2026-09-28 09:10 UTC45
  </span>
973f8fbdaf80atagen2026-09-28 09:10 UTC46
{/if}
973f8fbdaf80atagen2026-09-28 09:10 UTC47
973f8fbdaf80atagen2026-09-28 09:10 UTC48
<style>
973f8fbdaf80atagen2026-09-28 09:10 UTC49
  .mxc-avatar {
973f8fbdaf80atagen2026-09-28 09:10 UTC50
    flex: none;
973f8fbdaf80atagen2026-09-28 09:10 UTC51
    object-fit: cover;
05a48e9e7942atagen2026-10-24 09:32 UTC52
    border-radius: var(--r-md);
973f8fbdaf80atagen2026-09-28 09:10 UTC53
    background: var(--bg-soft);
973f8fbdaf80atagen2026-09-28 09:10 UTC54
  }
973f8fbdaf80atagen2026-09-28 09:10 UTC55
  .mxc-avatar.circle { border-radius: 50%; }
973f8fbdaf80atagen2026-09-28 09:10 UTC56
  .mxc-fallback {
973f8fbdaf80atagen2026-09-28 09:10 UTC57
    flex: none;
973f8fbdaf80atagen2026-09-28 09:10 UTC58
    display: inline-grid;
973f8fbdaf80atagen2026-09-28 09:10 UTC59
    place-items: center;
05a48e9e7942atagen2026-10-24 09:32 UTC60
    border-radius: var(--r-md);
05a48e9e7942atagen2026-10-24 09:32 UTC61
    color: #fff;
973f8fbdaf80atagen2026-09-28 09:10 UTC62
    font-weight: 700;
05a48e9e7942atagen2026-10-24 09:32 UTC63
    letter-spacing: 0.02em;
d07bca5da4aeatagen2026-10-26 03:48 UTC64
    line-height: 1;
d07bca5da4aeatagen2026-10-26 03:48 UTC65
    overflow: hidden;
973f8fbdaf80atagen2026-09-28 09:10 UTC66
  }
973f8fbdaf80atagen2026-09-28 09:10 UTC67
  .mxc-fallback.circle { border-radius: 50%; }
973f8fbdaf80atagen2026-09-28 09:10 UTC68
</style>