diff --git a/resources/views/email.phtml b/resources/views/email.phtml index d2d8097..93c99fe 100644 --- a/resources/views/email.phtml +++ b/resources/views/email.phtml @@ -20,9 +20,83 @@ use Illuminate\Support\Collection; * @var int $lookahead_days * @var int $historical_lookahead * @var int $generated_at + * @var array $avatar_cids xref => CID name * @var string $account_url */ +$avatar_size = 48; + +/** + * Inline HTML for a single circular avatar. + * + * Renders an if the dispatch service was able to + * resolve an image for the individual; otherwise renders a coloured + * circle with the person's initials. The placeholder is intentionally + * CSS-only — inline SVG and data: URIs are unreliable in Outlook / + * some webmail clients. + */ +$avatar = static function (Individual|null $individual) use ($avatar_cids, $avatar_size): string { + if (!$individual instanceof Individual) { + return ''; + } + + $alt = e(strip_tags($individual->fullName())); + + if (isset($avatar_cids[$individual->xref()])) { + $cid = $avatar_cids[$individual->xref()]; + + return '' . $alt . ''; + } + + // CSS-only fallback: coloured circle with initials. Hash the xref + // into a stable hue so each person keeps the same colour across + // newsletters. + $hue = hexdec(substr(md5($individual->xref()), 0, 2)) * 360 / 255; + $first = strip_tags($individual->getAllNames()[0]['givn'] ?? $individual->xref()); + $last = strip_tags($individual->getAllNames()[0]['surn'] ?? ''); + $i1 = mb_substr($first, 0, 1); + $i2 = mb_substr($last, 0, 1); + $initials = e(mb_strtoupper($i1 . $i2)); + + return '' . $initials . ''; +}; + +/** + * HTML for the avatar(s) attached to a Fact's primary record: + * a single circle for Individual facts, side-by-side circles for + * Family facts (anniversaries). + */ +$record_avatars = static function (Fact $fact) use ($avatar): string { + $record = $fact->record(); + + if ($record instanceof Individual) { + return $avatar($record); + } + + if ($record instanceof Family) { + $parts = []; + + foreach ([$record->husband(), $record->wife()] as $spouse) { + if ($spouse instanceof Individual) { + $parts[] = $avatar($spouse); + } + } + + // Slight negative margin so the two circles overlap a touch — + // visually communicates "couple" without needing extra glue. + return '' + . implode('', $parts) + . ''; + } + + return ''; +}; + $record_label = static function (Fact $fact): string { $record = $fact->record(); @@ -141,15 +215,25 @@ $anniversary_label = static function (int $age) use ($ordinal): string {

+ + isEmpty()) : ?>

-