Re-skin newsletter to BockenTheme light mode

Drops the editorial-serif palette in favour of the actual
website tokens so the newsletter and the site read as one
product:

- Cream #f8f6f1 page, surface #efecea card patches with #ddd
  hairline borders matching the .card component.
- Open Sans 300/400/500/600 throughout (Google Fonts @import,
  Helvetica/Arial fallback).
- Nord accent colours: nord10 blue for birth + links, nord3
  graphite for death, nord15 mauve for marriage anniversaries.
  Each event row's timeline dot is colour-coded to its event
  type, which gives the right-hand rail a quiet ribbon of
  meaning when several event types appear in one card.
- Soft elevation on cards (1px shadow), thin underlines on
  links — same affordance the site uses.
- Header/footer chrome simplified: small caps kicker in nord10,
  light-weight site title, no ornaments.
This commit is contained in:
2026-05-15 12:38:33 +02:00
parent 4ceade9079
commit a8511d2a1b
+143 -139
View File
@@ -24,27 +24,38 @@ use Illuminate\Support\Collection;
* @var string $account_url
*/
// ─── Aesthetic constants ────────────────────────────────────────────────
// ─── BockenTheme light-mode palette ─────────────────────────────────────
// Pulled from src/scss/theme.scss + config/_theme-variables.scss in the
// Bocken theme so the newsletter and the website read as one product.
$palette = [
'paper' => '#f8f1e3', // warm ivory background
'ink' => '#3a2820', // deep oxblood-brown body text
'gold' => '#a17536', // aged-gold accents
'rule' => '#d4be91', // hairline rule
'mute' => '#7a6a5e', // muted secondary text
'shadow' => '#5a4a3e', // for darker symbols (death)
'bg' => '#f8f6f1', // body / page background (--color-bg-primary)
'surface' => '#efecea', // raised section cards (--color-bg-secondary)
'elevated' => '#dfdcd8', // tertiary surface (--color-bg-elevated)
'border' => '#ddd', // hairline borders (--color-border)
'ink' => '#2a2a2a', // primary text (--color-text-primary)
'ink2' => '#555', // secondary text (--color-text-secondary)
'ink3' => '#777', // tertiary text (--color-text-tertiary)
'mute' => '#aaa', // muted text (--color-text-muted)
'link' => '#5E81AC', // nord10 — primary link
'link_hov' => '#81A1C1', // nord9
'accent' => '#BF616A', // nord11 — red FAB accent
'birth' => '#5E81AC', // nord10 — birth (blue)
'death' => '#4C566A', // nord3 — death (graphite)
'marr' => '#B48EAD', // nord15 — marriage (purple/pink)
];
$serif_display = "'EB Garamond', Georgia, 'Times New Roman', serif";
$serif_body = "Georgia, 'Iowan Old Style', 'Palatino Linotype', serif";
$font_stack = "'Open Sans', Helvetica, Arial, 'Noto Sans', sans-serif";
$avatar_size = 56;
// ─── Helpers ────────────────────────────────────────────────────────────
$linked_name = static function (Individual $individual): string {
$linked_name = static function (Individual $individual) use ($palette): string {
$name = strip_tags($individual->fullName());
$url = $individual->url();
$style = 'color:inherit;text-decoration:none;border-bottom:1px solid #c8a96a;';
$style = 'color:' . $palette['ink'] . ';text-decoration:none;'
. 'border-bottom:1px solid ' . $palette['border'] . ';'
. 'padding-bottom:1px;';
return '<a href="' . e($url) . '" style="' . $style . '">' . e($name) . '</a>';
};
@@ -82,38 +93,35 @@ $event_date_display = static function (Fact $fact): string {
};
$event_kind = static function (Fact $fact): string {
$tag = $fact->tag();
$parts = explode(':', $tag);
$parts = explode(':', $fact->tag());
return end($parts);
};
/**
* Compact inline SVG glyph for each event type. Rendered in modern
* webmail (Gmail web, Apple Mail, iOS, Outlook 365); Outlook desktop
* strips SVG silently — the textual event label still carries the
* meaning, so the email reads correctly without it.
* Compact inline SVG glyph for each event type. Coloured to match the
* BockenTheme Nord palette: birth in nord10 blue, death in nord3
* graphite, marriage in nord15 mauve.
*/
$event_icon = static function (string $kind) use ($palette): string {
$svg_open = '<svg xmlns="http://www.w3.org/2000/svg" style="vertical-align:-3px;" ';
$svg_open = '<svg xmlns="http://www.w3.org/2000/svg" style="vertical-align:-3px;flex:none;" ';
return match ($kind) {
// Eight-point star/sparkle for births.
// Eight-point sparkle — celebration.
'BIRT' => $svg_open . 'viewBox="0 0 24 24" width="18" height="18">'
. '<path fill="' . $palette['gold'] . '" d="M12 1 L13.4 9 L20.5 7.2 L15.4 12.5 L20.5 17.8 L13.4 16 L12 24 L10.6 16 L3.5 17.8 L8.6 12.5 L3.5 7.2 L10.6 9 Z"/>'
. '<path fill="' . $palette['birth'] . '" d="M12 1 L13.4 9 L20.5 7.2 L15.4 12.5 L20.5 17.8 L13.4 16 L12 24 L10.6 16 L3.5 17.8 L8.6 12.5 L3.5 7.2 L10.6 9 Z"/>'
. '</svg>',
// Latin obelus/dagger — the typographic convention for "died" in
// obituaries since the 18th century. Restrained, narrow.
// Latin obelus — the typographic mark for "died" in obituaries.
'DEAT' => $svg_open . 'viewBox="0 0 24 24" width="14" height="18">'
. '<rect x="10.5" y="2" width="3" height="20" fill="' . $palette['shadow'] . '"/>'
. '<rect x="5.5" y="7" width="13" height="3" fill="' . $palette['shadow'] . '"/>'
. '<rect x="10.5" y="2" width="3" height="20" fill="' . $palette['death'] . '"/>'
. '<rect x="5.5" y="7" width="13" height="3" fill="' . $palette['death'] . '"/>'
. '</svg>',
// Two interlocking rings — universal heraldic mark of marriage.
// Two interlocking rings — universal mark of marriage.
'MARR' => $svg_open . 'viewBox="0 0 36 22" width="28" height="18">'
. '<circle cx="13" cy="11" r="8" fill="none" stroke="' . $palette['gold'] . '" stroke-width="1.8"/>'
. '<circle cx="23" cy="11" r="8" fill="none" stroke="' . $palette['gold'] . '" stroke-width="1.8"/>'
. '<circle cx="13" cy="11" r="8" fill="none" stroke="' . $palette['marr'] . '" stroke-width="1.8"/>'
. '<circle cx="23" cy="11" r="8" fill="none" stroke="' . $palette['marr'] . '" stroke-width="1.8"/>'
. '</svg>',
default => '',
@@ -121,18 +129,25 @@ $event_icon = static function (string $kind) use ($palette): string {
};
/**
* A single circular avatar — either the embedded image, or a coloured
* disc with the person's initials. Wraps in an <a> linking to the
* individual's webtrees page when available.
* Map an event type to the colour used for that row's timeline dot
* (so the right rail reads as a colour-coded ribbon).
*/
$event_color = static function (string $kind) use ($palette): string {
return match ($kind) {
'BIRT' => $palette['birth'],
'DEAT' => $palette['death'],
'MARR' => $palette['marr'],
default => $palette['ink3'],
};
};
$avatar = static function (Individual|null $individual) use ($avatar_cids, $avatar_size, $palette): string {
if (!$individual instanceof Individual) {
return '';
}
$alt = e(strip_tags($individual->fullName()));
$shadow = 'box-shadow:0 0 0 1px ' . $palette['rule'] . ',0 2px 6px rgba(58,40,32,0.18);';
$shadow = 'box-shadow:0 0 0 1px ' . $palette['border'] . ',0 1px 3px rgba(0,0,0,0.08);';
if (isset($avatar_cids[$individual->xref()])) {
$cid = $avatar_cids[$individual->xref()];
@@ -140,6 +155,8 @@ $avatar = static function (Individual|null $individual) use ($avatar_cids, $avat
. ' width="' . $avatar_size . '" height="' . $avatar_size . '"'
. ' style="border-radius:50%;object-fit:cover;display:block;' . $shadow . '">';
} else {
// Initial-disc fallback. Hue is hashed from the xref so the
// same person keeps the same colour across editions.
$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'] ?? '');
@@ -147,9 +164,9 @@ $avatar = static function (Individual|null $individual) use ($avatar_cids, $avat
$inner = '<span aria-label="' . $alt . '" style="'
. 'display:block;width:' . $avatar_size . 'px;height:' . $avatar_size . 'px;'
. 'border-radius:50%;background:hsl(' . (int) $hue . ',38%,55%);color:#fff;'
. "font:600 20px/{$avatar_size}px Georgia,serif;text-align:center;"
. 'letter-spacing:0.5px;' . $shadow . '">' . $initials . '</span>';
. 'border-radius:50%;background:hsl(' . (int) $hue . ',32%,60%);color:#fff;'
. "font:600 19px/{$avatar_size}px " . $font_stack . ';text-align:center;'
. 'letter-spacing:0.3px;' . $shadow . '">' . $initials . '</span>';
}
return '<a href="' . e($individual->url()) . '" style="text-decoration:none;">' . $inner . '</a>';
@@ -171,21 +188,16 @@ $record_avatars = static function (Fact $fact) use ($avatar): string {
}
}
// Two circles overlapping by a few px reads as "couple" without
// needing additional glue characters.
return '<table cellpadding="0" cellspacing="0" border="0"><tr>'
. '<td>' . ($parts[0] ?? '') . '</td>'
. '<td style="padding-left:0;width:0;"></td>'
. (isset($parts[1])
? '<td style="padding-left:8px;">' . $parts[1] . '</td>'
: '')
. (isset($parts[1]) ? '<td style="padding-left:8px;">' . $parts[1] . '</td>' : '')
. '</tr></table>';
}
return '';
};
// Locale-aware ordinal: English uses st/nd/rd/th; German just appends ".".
// Locale-aware ordinal: German uses "N.", English uses st/nd/rd/th.
$ordinal = static function (int $n): string {
if (str_starts_with(I18N::languageTag(), 'de')) {
return $n . '.';
@@ -240,48 +252,6 @@ $anniversary_label = static function (int $age) use ($ordinal): string {
: I18N::translate('Wedding anniversary');
};
// Styles reused across the event lists.
$row_padding = '20px 0';
$divider_style = 'border-bottom:1px solid ' . $palette['rule'] . ';';
$section_title_style = 'margin:48px 0 8px;'
. 'font-family:' . $serif_display . ';'
. 'font-weight:500;font-size:28px;line-height:1.1;'
. 'color:' . $palette['ink'] . ';'
. 'letter-spacing:0.005em;';
$section_kicker_style = 'margin:0 0 22px;'
. 'font-family:' . $serif_display . ';'
. 'font-style:italic;font-size:15px;'
. 'color:' . $palette['mute'] . ';'
. 'letter-spacing:0.02em;';
$timeline_cell_style = 'width:170px;vertical-align:top;'
. 'padding:24px 0 24px 28px;'
. 'border-left:1px solid ' . $palette['rule'] . ';'
. 'font-family:' . $serif_body . ';'
. 'color:' . $palette['mute'] . ';'
. 'font-size:12px;letter-spacing:0.12em;text-transform:uppercase;'
. 'white-space:nowrap;';
$timeline_dot_style = 'display:inline-block;'
. 'width:11px;height:11px;background:' . $palette['gold'] . ';'
. 'border-radius:50%;'
. 'margin-left:-34px;margin-right:14px;vertical-align:middle;';
$content_cell_style = 'vertical-align:middle;'
. 'padding:24px 24px 24px 18px;'
. 'font-family:' . $serif_body . ';font-size:16px;line-height:1.4;'
. 'color:' . $palette['ink'] . ';';
$avatar_cell_style = 'width:64px;vertical-align:middle;padding:18px 0;';
// Renders one event row in the consistent 3-column layout shared by all
// sections (avatar | content | timeline + date).
/**
* Locale-aware long-form date for the masthead. German edition reads
* "15. Mai 2026" rather than the English "May 15, 2026".
*/
$masthead_date = static function (int $timestamp): string {
if (str_starts_with(I18N::languageTag(), 'de')) {
$months = [
@@ -297,150 +267,184 @@ $masthead_date = static function (int $timestamp): string {
return date('F j, Y', $timestamp);
};
$event_row = static function (Fact $fact, string $body_html)
// ─── Row styles ─────────────────────────────────────────────────────────
$section_title_style = 'margin:0 0 4px;'
. 'font-family:' . $font_stack . ';'
. 'font-weight:400;font-size:24px;line-height:1.2;'
. 'color:' . $palette['ink'] . ';'
. 'letter-spacing:-0.005em;';
$section_kicker_style = 'margin:0 0 18px;'
. 'font-family:' . $font_stack . ';'
. 'font-style:italic;font-weight:300;font-size:14px;'
. 'color:' . $palette['ink3'] . ';';
$divider_style = 'border-bottom:1px solid ' . $palette['border'] . ';';
$avatar_cell_style = 'width:72px;vertical-align:middle;padding:18px 0 18px 18px;';
$content_cell_style = 'vertical-align:middle;'
. 'padding:18px 18px 18px 16px;'
. 'font-family:' . $font_stack . ';font-size:15px;line-height:1.4;'
. 'font-weight:300;color:' . $palette['ink'] . ';';
$timeline_cell_style = 'width:170px;vertical-align:middle;'
. 'padding:18px 18px 18px 26px;'
. 'border-left:1px solid ' . $palette['border'] . ';'
. 'font-family:' . $font_stack . ';'
. 'font-weight:500;font-size:11px;letter-spacing:0.12em;text-transform:uppercase;'
. 'color:' . $palette['ink3'] . ';white-space:nowrap;';
// Renders one event row in the consistent 3-column layout shared by
// every section (avatar | content | date on the timeline rail).
$event_row = static function (Fact $fact, string $body_html, string $dot_color)
use (
$record_avatars,
$event_date_display,
$avatar_cell_style,
$content_cell_style,
$timeline_cell_style,
$timeline_dot_style,
$divider_style,
): string {
return '<tr style="' . $divider_style . '">'
. '<td style="' . $avatar_cell_style . '">' . $record_avatars($fact) . '</td>'
. '<td style="' . $content_cell_style . '">' . $body_html . '</td>'
. '<td style="' . $timeline_cell_style . '">'
. '<span style="' . $timeline_dot_style . '"></span>'
. '<span style="display:inline-block;width:10px;height:10px;background:'
. $dot_color . ';border-radius:50%;'
. 'margin-left:-32px;margin-right:14px;vertical-align:middle;"></span>'
. e($event_date_display($fact))
. '</td>'
. '</tr>';
};
// Wraps a list of <tr> rows in the "card" surface — rounded background
// patch matching .card on the website.
$card_open = '<table role="presentation" cellpadding="0" cellspacing="0" border="0" '
. 'style="width:100%;border-collapse:collapse;'
. 'background:' . $palette['surface'] . ';'
. 'border:1px solid ' . $palette['border'] . ';'
. 'border-radius:8px;'
. 'box-shadow:0 1px 3px rgba(0,0,0,0.04);'
. 'overflow:hidden;">';
$card_close = '</table>';
?><!doctype html>
<html lang="<?= e(I18N::languageTag()) ?>">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= e(I18N::translate('Family newsletter — %s', date('F j, Y', $generated_at))) ?></title>
<title><?= e(I18N::translate('Family newsletter — %s', $masthead_date($generated_at))) ?></title>
<style>
@import url('https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400;0,500;0,600;1,400&display=swap');
body { margin: 0; padding: 0; background: <?= $palette['paper'] ?>; }
a:hover { opacity: 0.75; }
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300;0,400;0,500;0,600;1,300;1,400&display=swap');
body { margin: 0; padding: 0; background: <?= $palette['bg'] ?>; }
a:hover { color: <?= $palette['link_hov'] ?> !important; }
.nl-tr:last-child { border-bottom: 0 !important; }
</style>
</head>
<body style="margin:0;padding:0;background:<?= $palette['paper'] ?>;color:<?= $palette['ink'] ?>;">
<body style="margin:0;padding:0;background:<?= $palette['bg'] ?>;color:<?= $palette['ink'] ?>;">
<table role="presentation" cellpadding="0" cellspacing="0" border="0"
style="width:100%;background:<?= $palette['paper'] ?>;">
style="width:100%;background:<?= $palette['bg'] ?>;">
<tr>
<td align="center" style="padding:32px 16px;">
<td align="center" style="padding:36px 16px;">
<table role="presentation" cellpadding="0" cellspacing="0" border="0"
style="width:100%;max-width:720px;background:<?= $palette['paper'] ?>;">
style="width:100%;max-width:720px;background:<?= $palette['bg'] ?>;font-family:<?= $font_stack ?>;">
<!-- Masthead ─────────────────────────────────────────── -->
<tr>
<td style="padding:24px 8px 8px;text-align:center;">
<div style="font-family:<?= $serif_display ?>;font-style:italic;font-size:13px;letter-spacing:0.4em;text-transform:uppercase;color:<?= $palette['gold'] ?>;">
<td style="padding:8px 8px 24px;">
<div style="font-size:11px;font-weight:600;letter-spacing:0.22em;text-transform:uppercase;color:<?= $palette['link'] ?>;">
<?= e(I18N::translate('Family Chronicle')) ?>
</div>
<h1 style="margin:14px 0 6px;font-family:<?= $serif_display ?>;font-weight:500;font-size:46px;line-height:1.05;letter-spacing:-0.005em;color:<?= $palette['ink'] ?>;">
<h1 style="margin:10px 0 6px;font-weight:300;font-size:38px;line-height:1.1;letter-spacing:-0.015em;color:<?= $palette['ink'] ?>;">
<?= e($tree->title()) ?>
</h1>
<div style="font-family:<?= $serif_body ?>;font-size:14px;color:<?= $palette['mute'] ?>;letter-spacing:0.05em;">
<div style="font-size:13px;font-weight:300;color:<?= $palette['ink3'] ?>;">
<?= e($masthead_date($generated_at)) ?>
</div>
<div style="margin:22px auto 8px;width:80px;border-top:1px solid <?= $palette['gold'] ?>;"></div>
<div style="font-family:<?= $serif_display ?>;font-size:18px;color:<?= $palette['gold'] ?>;letter-spacing:0.3em;"></div>
</td>
</tr>
<!-- Intro ────────────────────────────────────────────── -->
<tr>
<td style="padding:14px 16px 4px;text-align:center;font-family:<?= $serif_body ?>;font-style:italic;font-size:16px;color:<?= $palette['mute'] ?>;">
<span style="color:<?= $palette['mute'] ?>;">·</span>
<?= e(I18N::translate('Events in the next %d days.', $lookahead_days)) ?>
</div>
</td>
</tr>
<?php if (!$birthdays->isEmpty()) : ?>
<tr><td style="padding:0 16px;">
<tr><td style="padding:8px 0 0;">
<h2 style="<?= $section_title_style ?>"><?= e(I18N::translate('Upcoming birthdays')) ?></h2>
<p style="<?= $section_kicker_style ?>">
<?= e(I18N::translate('Living kin who will celebrate this fortnight.')) ?>
</p>
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="width:100%;border-collapse:collapse;">
<?= $card_open ?>
<?php foreach ($birthdays as $fact) : ?>
<?php
$age = $upcoming_age($fact);
$body = $event_icon('BIRT')
. '<span style="margin-left:10px;">'
. '<strong style="font-weight:600;">' . $record_label($fact) . '</strong>'
. ' &mdash; <span style="color:' . $palette['mute'] . ';">'
. e($birthday_label($age)) . '</span>'
$body = '<span style="display:inline-block;vertical-align:middle;margin-right:12px;">' . $event_icon('BIRT') . '</span>'
. '<span style="vertical-align:middle;">'
. '<span style="font-weight:600;color:' . $palette['ink'] . ';">' . $record_label($fact) . '</span>'
. ' <span style="color:' . $palette['ink2'] . ';font-weight:300;">— ' . e($birthday_label($age)) . '</span>'
. '</span>';
echo $event_row($fact, $body);
echo $event_row($fact, $body, $palette['birth']);
?>
<?php endforeach ?>
</table>
<?= $card_close ?>
</td></tr>
<?php endif ?>
<?php if ($include_anniversaries && $anniversaries !== null && !$anniversaries->isEmpty()) : ?>
<tr><td style="padding:0 16px;">
<tr><td style="padding:32px 0 0;">
<h2 style="<?= $section_title_style ?>"><?= e(I18N::translate('Upcoming marriage anniversaries')) ?></h2>
<p style="<?= $section_kicker_style ?>">
<?= e(I18N::translate('Marriages still intact.')) ?>
</p>
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="width:100%;border-collapse:collapse;">
<?= $card_open ?>
<?php foreach ($anniversaries as $fact) : ?>
<?php
$age = $upcoming_age($fact);
$body = $event_icon('MARR')
. '<span style="margin-left:10px;">'
. '<strong style="font-weight:600;">' . $record_label($fact) . '</strong>'
. ' &mdash; <span style="color:' . $palette['mute'] . ';">'
. e($anniversary_label($age)) . '</span>'
$body = '<span style="display:inline-block;vertical-align:middle;margin-right:12px;">' . $event_icon('MARR') . '</span>'
. '<span style="vertical-align:middle;">'
. '<span style="font-weight:600;color:' . $palette['ink'] . ';">' . $record_label($fact) . '</span>'
. ' <span style="color:' . $palette['ink2'] . ';font-weight:300;">— ' . e($anniversary_label($age)) . '</span>'
. '</span>';
echo $event_row($fact, $body);
echo $event_row($fact, $body, $palette['marr']);
?>
<?php endforeach ?>
</table>
<?= $card_close ?>
</td></tr>
<?php endif ?>
<?php if ($include_historical && $historical !== null && !$historical->isEmpty()) : ?>
<tr><td style="padding:0 16px;">
<tr><td style="padding:32px 0 0;">
<h2 style="<?= $section_title_style ?>"><?= e(I18N::translate('On this month in history')) ?></h2>
<p style="<?= $section_kicker_style ?>">
<?= e(I18N::translate('Events in the next %d days for people who have passed away.', $historical_lookahead)) ?>
</p>
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="width:100%;border-collapse:collapse;">
<?= $card_open ?>
<?php foreach ($historical as $fact) : ?>
<?php
$kind = $event_kind($fact);
$body = $event_icon($kind)
. '<span style="margin-left:10px;">'
. '<strong style="font-weight:600;">' . $record_label($fact) . '</strong>'
. ' &mdash; <span style="color:' . $palette['mute'] . ';">'
. e($fact->label()) . '</span>'
$body = '<span style="display:inline-block;vertical-align:middle;margin-right:12px;">' . $event_icon($kind) . '</span>'
. '<span style="vertical-align:middle;">'
. '<span style="font-weight:600;color:' . $palette['ink'] . ';">' . $record_label($fact) . '</span>'
. ' <span style="color:' . $palette['ink2'] . ';font-weight:300;">— ' . e($fact->label()) . '</span>'
. '</span>';
echo $event_row($fact, $body);
echo $event_row($fact, $body, $event_color($kind));
?>
<?php endforeach ?>
</table>
<?= $card_close ?>
</td></tr>
<?php endif ?>
<!-- Footer ─────────────────────────────────────────── -->
<tr><td style="padding:48px 16px 24px;text-align:center;">
<div style="font-family:<?= $serif_display ?>;font-size:18px;color:<?= $palette['gold'] ?>;letter-spacing:0.3em;"></div>
<p style="margin:22px auto 0;max-width:520px;font-family:<?= $serif_body ?>;font-size:13px;line-height:1.6;color:<?= $palette['mute'] ?>;font-style:italic;">
<tr><td style="padding:40px 8px 8px;">
<div style="height:1px;background:<?= $palette['border'] ?>;margin-bottom:18px;"></div>
<p style="margin:0;font-size:12px;line-height:1.6;font-weight:300;color:<?= $palette['ink3'] ?>;">
<?= e(I18N::translate('You are receiving this email because you subscribed to the %s newsletter.', $tree->title())) ?>
<br>
<?= I18N::translate(
'To change or cancel your subscription, edit the “Newsletter subscription” section on your %s page.',
'<a href="' . e($account_url) . '" style="color:' . $palette['gold'] . ';text-decoration:none;border-bottom:1px solid ' . $palette['gold'] . ';">' . e(I18N::translate('My account')) . '</a>',
'<a href="' . e($account_url) . '" style="color:' . $palette['link'] . ';text-decoration:none;border-bottom:1px solid ' . $palette['link'] . ';">' . e(I18N::translate('My account')) . '</a>',
) ?>
</p>
</td></tr>