\n");
foreach (var grp in groups)
{
var isSolo = grp.Key.StartsWith("__solo__", StringComparison.Ordinal);
var groupName = isSolo ? null : grp.Key;
var memberList = grp.ToList();
// Determine group gender label
var genders = memberList.Select(a => Str(a["gender"]) ?? "unknown").Distinct().ToList();
string genderLabel = genders.Count == 1
? genders[0] switch { "female" => "Weibchen", "male" => "Männchen", _ => "" }
: genders.Any(g => g != "unknown") ? "Gemischt" : "";
sb.Append("
\n");
sb.Append(" \n");
// Gallery: first photo of each animal in the group
var gallery = memberList
.Select(a => (
url: (a["photos"] as JsonArray ?? []).FirstOrDefault()?.GetValue(),
name: Str(a["name"]) ?? ""))
.Where(x => !string.IsNullOrEmpty(x.url))
.ToList();
if (gallery.Count > 0)
{
sb.Append(" \n");
foreach (var (url, name) in gallery)
sb.Append($"
}\")
\n");
sb.Append("
\n");
}
// Per-animal detail rows
sb.Append(" \n");
foreach (var a in memberList)
{
var name = H(Str(a["name"]) ?? "");
var farbe = Str(a["farbschlag"]);
var dob = Str(a["dateOfBirth"]);
var note = Str(a["characterNote"]);
var saleText = Str(a["aiSaleText"]);
sb.Append(" - \n");
sb.Append($" {name}\n");
var details = new List();
if (!string.IsNullOrEmpty(farbe)) details.Add(H(farbe));
if (!string.IsNullOrEmpty(dob) && DateOnly.TryParse(dob, out var dob2))
details.Add($"* {dob2.ToString("MMMM yyyy", System.Globalization.CultureInfo.GetCultureInfo("de-DE"))}");
if (details.Count > 0)
sb.Append($" {string.Join(" · ", details)}\n");
var personalityText = note ?? saleText;
if (!string.IsNullOrWhiteSpace(personalityText))
sb.Append($" {H(personalityText)}\n");
sb.Append("
\n");
}
sb.Append("
\n");
sb.Append(" \n");
}
sb.Append("
\n");
sb.Append("\n");
return sb.ToString();
}
// ── Page template ────────────────────────────────────────────────────
private static string PageHtml(
string title, string? seoDesc, string body,
IReadOnlyList