Initial commit 🚀
This commit is contained in:
commit
fd58a8d32f
235 files changed
+19775
No files matched your search
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* The alpha sign-up confirmation email, in the site's voice and look: warm paper, warm black,
|
||||
* one neo-grotesque, hairlines, one square button. Email clients ignore most CSS, so the layout
|
||||
* is tables with inline styles, the type falls back from Inter Tight to Helvetica and Arial, and
|
||||
* the logo travels as an inline attachment (cid) rather than a remote image.
|
||||
*
|
||||
* Colours are the tokens from src/styles/tokens.css, written out: email has no custom properties.
|
||||
* The hairline is --line (ink at 14 %) flattened onto paper.
|
||||
*/
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const C = {
|
||||
paper: '#E9E5DF',
|
||||
ink: '#1A1A18',
|
||||
muted: '#57564F',
|
||||
line: '#CCC9C3',
|
||||
};
|
||||
const FONT = "'Inter Tight', Inter, 'Helvetica Neue', Helvetica, Arial, sans-serif";
|
||||
const LOGO_CID = 'logo@ciphercell.de';
|
||||
const LOGO = { path: fileURLToPath(new URL('./logo.png', import.meta.url)), width: 140, height: 32 };
|
||||
|
||||
const COPY = {
|
||||
en: {
|
||||
subject: 'Confirm your CipherVerifier alpha registration',
|
||||
preheader: 'One click confirms your registration for the CipherVerifier alpha.',
|
||||
title: 'Confirm your registration.',
|
||||
body: (email) => `You registered ${email} for the CipherVerifier alpha. Confirm the address, and we will email you once, when the alpha is released. Nothing else.`,
|
||||
button: 'Confirm registration',
|
||||
expiry: 'The link is valid for 7 days. If you did not register, ignore this email. Your address is not stored.',
|
||||
fallback: 'If the button does not work, open this address in your browser:',
|
||||
},
|
||||
de: {
|
||||
subject: 'Bitte bestätigen Sie Ihre Registrierung für die CipherVerifier-Alpha',
|
||||
preheader: 'Ein Klick bestätigt Ihre Registrierung für die Alpha von CipherVerifier.',
|
||||
title: 'Registrierung bestätigen.',
|
||||
body: (email) => `Sie haben ${email} für die Alpha von CipherVerifier registriert. Bestätigen Sie die Adresse, und wir schreiben Ihnen einmal, sobald die Alpha erscheint. Sonst nichts.`,
|
||||
button: 'Registrierung bestätigen',
|
||||
expiry: 'Der Link ist 7 Tage gültig. Wenn Sie sich nicht registriert haben, ignorieren Sie diese E-Mail. Ihre Adresse wird nicht gespeichert.',
|
||||
fallback: 'Falls der Button nicht funktioniert, öffnen Sie diese Adresse im Browser:',
|
||||
},
|
||||
};
|
||||
const ADDRESS = ['CipherCell', 'Innstr. 43, 94032 Passau'];
|
||||
const SITE_LABEL = 'www.ciphercell.de';
|
||||
|
||||
const esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]);
|
||||
|
||||
/** { subject, text, html, attachments } for nodemailer. */
|
||||
export function confirmMail({ lang, email, link, site }) {
|
||||
const t = COPY[lang] ?? COPY.en;
|
||||
const text = [
|
||||
t.title,
|
||||
'',
|
||||
t.body(email),
|
||||
'',
|
||||
`${t.button}:`,
|
||||
link,
|
||||
'',
|
||||
t.expiry,
|
||||
'',
|
||||
'-- ',
|
||||
...ADDRESS,
|
||||
SITE_LABEL,
|
||||
].join('\n');
|
||||
|
||||
const p = (content, { size = 16, color = C.ink, lineHeight = 1.55, extra = '' } = {}) =>
|
||||
`<p style="margin:0;font-family:${FONT};font-size:${size}px;line-height:${lineHeight};color:${color};${extra}">${content}</p>`;
|
||||
const row = (content, padding) => `<tr><td style="padding:${padding};">${content}</td></tr>`;
|
||||
const rule = `<tr><td style="padding:0;border-top:1px solid ${C.line};font-size:0;line-height:0;"> </td></tr>`;
|
||||
|
||||
const html = `<!doctype html>
|
||||
<html lang="${lang}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="color-scheme" content="light">
|
||||
<meta name="supported-color-schemes" content="light">
|
||||
<title>${esc(t.subject)}</title>
|
||||
</head>
|
||||
<body style="margin:0;padding:0;background:${C.paper};-webkit-text-size-adjust:100%;">
|
||||
<div style="display:none;max-height:0;overflow:hidden;opacity:0;color:${C.paper};">${esc(t.preheader)}</div>
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:${C.paper};">
|
||||
<tr><td align="left" style="padding:40px 24px 48px;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="max-width:560px;">
|
||||
${row(`<a href="${esc(site)}" style="text-decoration:none;"><img src="cid:${LOGO_CID}" width="${LOGO.width}" height="${LOGO.height}" alt="CipherCell" style="display:block;border:0;width:${LOGO.width}px;height:${LOGO.height}px;font-family:${FONT};font-size:18px;font-weight:600;color:${C.ink};"></a>`, '0 0 20px')}
|
||||
${rule}
|
||||
${row(p(esc(t.title), { size: 34, lineHeight: 1.08, extra: 'font-weight:500;letter-spacing:-0.02em;' }), '48px 0 20px')}
|
||||
${row(p(esc(t.body(email))), '0 0 32px')}
|
||||
${row(`<table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr><td bgcolor="${C.ink}" style="background:${C.ink};">
|
||||
<a href="${esc(link)}" style="display:inline-block;padding:15px 26px;font-family:${FONT};font-size:16px;line-height:1;font-weight:500;color:${C.paper};text-decoration:none;">${esc(t.button)}</a>
|
||||
</td></tr></table>`, '0 0 32px')}
|
||||
${row(p(esc(t.expiry), { size: 14, color: C.muted, lineHeight: 1.4 }), '0 0 16px')}
|
||||
${row(p(`${esc(t.fallback)}<br><a href="${esc(link)}" style="color:${C.muted};text-decoration:underline;word-break:break-all;">${esc(link)}</a>`, { size: 14, color: C.muted, lineHeight: 1.4 }), '0 0 48px')}
|
||||
${rule}
|
||||
${row(p(`${ADDRESS.map(esc).join('<br>')}<br><a href="${esc(site)}" style="color:${C.muted};text-decoration:underline;">${SITE_LABEL}</a>`, { size: 14, color: C.muted, lineHeight: 1.4 }), '20px 0 0')}
|
||||
</table>
|
||||
</td></tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
return {
|
||||
subject: t.subject,
|
||||
text,
|
||||
html,
|
||||
attachments: [{ filename: 'ciphercell.png', path: LOGO.path, cid: LOGO_CID }],
|
||||
};
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Reference in new issue
Block a user