<%# Markup for the avatar demo.
The accessible name is owned solely by the container (role="img" + aria-label);
the inner <img> and fallback use aria-hidden="true" to avoid double announcement.
The library only tracks the source and its load state, exposing empty / loading /
loaded / error on data-state; shape, color, and size are drawn by demo.css. %>
<div class="avatar-demo">
<%# 1. An avatar that loads successfully. The src is a self-contained data-URI SVG that
draws a person silhouette (a picture, not letters) so the "loaded image" state reads
clearly different from the initials fallback below. %>
<figure class="avatar-demo__item">
<span
class="avatar"
data-controller="stimeo--avatar"
role="img"
aria-label="<%= t("components.avatar.demo.name_ok") %>"
data-stimeo--avatar-src-value="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='96'%20height='96'%3E%3Cdefs%3E%3ClinearGradient%20id='g'%20x1='0'%20y1='0'%20x2='0'%20y2='1'%3E%3Cstop%20offset='0'%20stop-color='%236366f1'/%3E%3Cstop%20offset='1'%20stop-color='%234338ca'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect%20width='96'%20height='96'%20fill='url(%23g)'/%3E%3Ccircle%20cx='48'%20cy='38'%20r='17'%20fill='%23eef2ff'/%3E%3Cpath%20d='M20%2084%20a28%2028%200%200%201%2056%200%20Z'%20fill='%23eef2ff'/%3E%3C/svg%3E">
<img
class="avatar__image"
alt=""
aria-hidden="true"
data-stimeo--avatar-target="image"
data-action="load->stimeo--avatar#onLoad error->stimeo--avatar#onError">
<span
class="avatar__fallback"
aria-hidden="true"
hidden
data-stimeo--avatar-target="fallback"
>AU</span>
</span>
<figcaption><%= t("components.avatar.demo.caption_ok") %></figcaption>
</figure>
<%# 2. An avatar that fails to load (nonexistent path → fallback initials). The browser
logs an expected 404 for the missing image — that is the point: it triggers the error
→ fallback path the library handles. %>
<figure class="avatar-demo__item">
<span
class="avatar"
data-controller="stimeo--avatar"
role="img"
aria-label="<%= t("components.avatar.demo.name_fallback") %>"
data-stimeo--avatar-src-value="/this-image-does-not-exist.jpg">
<img
class="avatar__image"
alt=""
aria-hidden="true"
data-stimeo--avatar-target="image"
data-action="load->stimeo--avatar#onLoad error->stimeo--avatar#onError">
<span
class="avatar__fallback"
aria-hidden="true"
hidden
data-stimeo--avatar-target="fallback"
>JD</span>
</span>
<figcaption><%= t("components.avatar.demo.caption_fallback") %></figcaption>
</figure>
<%# 3. An avatar with no image target at all. Both targets are optional, so a
fallback-only avatar is valid and settles in the empty state. %>
<figure class="avatar-demo__item">
<span
class="avatar"
data-controller="stimeo--avatar"
role="img"
aria-label="<%= t("components.avatar.demo.name_empty") %>">
<span
class="avatar__fallback"
aria-hidden="true"
data-stimeo--avatar-target="fallback"
>GU</span>
</span>
<figcaption><%= t("components.avatar.demo.caption_empty") %></figcaption>
</figure>
</div>
/* Presentation CSS for avatar. The library only tracks load state and toggles
data-state / hidden, so shape, color, size, and the fallback look are all here. */
.avatar-demo {
display: flex;
gap: 2rem;
flex-wrap: wrap;
}
.avatar-demo__item {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
margin: 0;
/* Cap the column width so a longer caption wraps under the avatar (centered)
instead of stretching the item and breaking the row layout. */
max-width: 12rem;
font-size: 0.85rem;
color: var(--color-text-muted);
text-align: center;
}
.avatar-demo__item figcaption {
text-wrap: balance;
}
/* The container owns the circular clip; the inner image / fallback are layered inside. */
.avatar {
position: relative;
display: inline-grid;
place-items: center;
width: 96px;
height: 96px;
border-radius: 50%;
overflow: hidden;
background: var(--surface-subtle);
box-shadow: 0 0 0 2px var(--surface-subtle), 0 1px 4px rgba(15, 23, 42, 0.2);
}
.avatar__image {
width: 100%;
height: 100%;
object-fit: cover;
}
/* Fallback initials. Visible only once the hidden attribute is removed. */
.avatar__fallback {
font-size: 2rem;
font-weight: 600;
color: var(--white);
background: var(--slate-500);
width: 100%;
height: 100%;
display: grid;
place-items: center;
}
/* data-state is set by the library. Example: a subtle pulse while loading. */
.avatar[data-state="loading"] {
animation: avatar-pulse 1.2s ease-in-out infinite;
}
@keyframes avatar-pulse {
50% {
opacity: 0.6;
}
}
@media (prefers-reduced-motion: reduce) {
.avatar[data-state="loading"] {
animation: none;
}
}