diff --git a/CLAUDE.md b/CLAUDE.md
index f0b532c..5ca6cbc 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -224,6 +224,11 @@ supplies:
with its own `PublicAct`: joining an event (owner decision 2026-10-05) is asked once more, since the event's server
shows who takes part. An event card shows the persona's participation (`privapub.event.participation`: pending,
accepted, rejected) with Join or Leave, except for events by invitation or taken on another site (`join_mode`).
+- **What a card shows of other platforms:** custom emoji (`Helpers/CustomEmojis.cs`: a post's or account's `:shortcode:`
+ in its text, name, warning, bio and fields, as the picture the server listed, never inside a tag), a threadiverse
+ post's downvotes next to its favourites, blurhash placeholders until a picture loads (`wwwroot/js/blurhash.js`
+ watches the page for `img[data-blurhash]`), a picture's focal point (`object-position`), and an event's online
+ link, participants and status.
- **Profiles and search** (`Pages/Profile.razor` at `/account/{id}`, `Pages/Search.razor` at `/search?q=`): a card's
author and a notification's account open the profile in the app; "Open original" is the only way to the remote site.
A card in a fixed-height flex column with `overflow-hidden` needs `flex-none`, or it collapses to nothing.
diff --git a/Components/Content.razor b/Components/Content.razor
index 20fc44c..7aa30d1 100644
--- a/Components/Content.razor
+++ b/Components/Content.razor
@@ -54,7 +54,7 @@
{
- @booster.Name
+ @CustomEmojis.Text(booster.Name, booster.Emojis)
@Localizer["boosted"]
}
@@ -81,7 +81,7 @@
- @status.Account?.Name
+ @CustomEmojis.Text(status.Account?.Name, status.Account?.Emojis ?? [])
@("@" + status.Account?.Acct)
@@ -137,7 +137,7 @@
- @spoiler
+ @CustomEmojis.Text(spoiler, status.Emojis)
@Body(status)
@@ -229,6 +229,13 @@
@status.FavouritesCount
}
+ @* a threadiverse post's downvotes: its upvotes are its favourites *@
+ @if (status.Privapub?.Votes is { Down: > 0 } votes)
+ {
+
+ @votes.Down
+
+ }
@@ -400,7 +407,7 @@
RenderFragment Body(Status status) => @
@if (status.Content is { Length: > 0 } content)
{
-
@((MarkupString)content)
+
@((MarkupString)CustomEmojis.Html(content, status.Emojis))
}
@if (status.MediaAttachments.Count > 0)
@@ -492,6 +499,25 @@
{
@(venue.Name ?? venue.Address ?? venue.Url)
}
+ @if (IsWebLink(happening.OnlineUrl))
+ {
+
@Localizer["Online"]
+ }
+ @if (happening.Participants is { } going)
+ {
+
+ @(happening.Capacity is { } capacity ? Localizer["{0} of {1} going", going, capacity] : Localizer["{0} going", going])
+
+ }
+ @switch (happening.Status?.ToUpperInvariant())
+ {
+ case "CANCELLED":
+
@Localizer["Cancelled"]
+ break;
+ case "TENTATIVE":
+
@Localizer["Tentative"]
+ break;
+ }
@if (CanJoin(happening))
{
@@ -544,7 +570,7 @@
{
"image" => @
+ src="@(media.PreviewUrl ?? media.Url)" data-blurhash="@media.Blurhash" style="@FocalPoint(media)" />
,
"gifv" => @
,
"video" => @