Files
Mathew 9b6638eb00
Deploy / deploy (push) Successful in 1m13s
Ajoute une couche de micro-animations sur tout le site
Deux courbes partagées dans lib/motion.ts, pour que le CSS et Framer
aient le même feeling, et quatre primitives que Tailwind ne sait pas
exprimer : reflet de bouton, halo qui suit le curseur, soulignement qui
se dessine, secousse d'erreur.

Nouveaux composants : Stagger (les collections se distribuent une par
une), Spotlight (trouve son parent tout seul, donc les cartes restent
des composants serveur), ScrollProgress, BackToTop, Parallax, Spinner,
CheckMark. Reveal accepte maintenant une direction.

Côté rendu : le trait de nav glisse d'un onglet à l'autre, les cartes
se soulèvent, le hero réagit au scroll, les piliers entrent en zig-zag,
la lightbox glisse dans le sens demandé et les formulaires répondent.

prefers-reduced-motion est respecté partout : Framer coupe via
useReducedMotion, le CSS via la media query déjà en place.

Le commit embarque aussi la réorganisation des composants par domaine
qui était en cours dans l'arbre de travail — les deux étaient trop
imbriquées pour être séparées proprement.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 16:04:41 +02:00

182 lines
8.3 KiB
TypeScript

// Email templates for the contact form.
// Brand-forward but email-client-safe: black header/footer bands (logo + gold,
// the site's identity) wrapping a LIGHT body with dark text. Dark text on a
// light body is the one combination Gmail dark mode never inverts, so it stays
// readable everywhere — unlike a fully dark email, whose light text Gmail flips
// to dark and hides.
// Table layout + inline styles + bgcolor attributes for client compatibility.
import { SITE_URL } from "@/lib/site";
const COLORS = {
band: "#0a0a0a", // black brand bands (header/footer) — the site's black
page: "#e9e4d8", // warm parchment page background
card: "#fbfaf6", // near-white body card
border: "#ddd6c6", // warm hairline border
rule: "#c4b896", // gold accent rule
ink: "#1a1714", // near-black body text
inkSoft: "#5a5346", // secondary text
accent: "#8a7338", // gold, darkened so it reads on the light body
accentOnDark: "#c4b896", // site gold, used on the black bands
box: "#f1ece0", // quoted-message background
};
const DISPLAY_FONT = '"Arial Narrow", "Helvetica Neue Condensed", "Helvetica Neue", Arial, sans-serif';
const SANS_FONT = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif';
const STUDIO_NAME = "Highland Games Studio";
const TAGLINE = "Where summits become worlds.";
// Loaded from the live site — the asset must be deployed there to render.
const LOGO_EMAIL_URL = `${SITE_URL}/image/Studio/Full%20Logo.png`;
function escapeHtml(value: string): string {
return value
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
function nl2br(value: string): string {
return escapeHtml(value).replace(/\n/g, "<br />");
}
type ContactInput = {
name: string;
email: string;
subject: string;
message: string;
};
// Small gold, wide-tracked eyebrow label — the site's signature accent.
function eyebrow(text: string): string {
return `<div style="font-family:${DISPLAY_FONT};font-size:12px;letter-spacing:5px;color:${COLORS.accent};text-transform:uppercase;">${text}</div>`;
}
// Big condensed uppercase heading, in near-black ink.
function heading(text: string): string {
return `<div style="font-family:${DISPLAY_FONT};font-size:36px;line-height:1.05;letter-spacing:1px;color:${COLORS.ink};text-transform:uppercase;margin:10px 0 0;">${text}</div>`;
}
function shell(innerHtml: string): string {
return `<!doctype html>
<html lang="en">
<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" />
</head>
<body style="margin:0;padding:0;background:${COLORS.page};">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" bgcolor="${COLORS.page}" style="background:${COLORS.page};padding:32px 12px;">
<tr>
<td align="center">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" bgcolor="${COLORS.card}" style="max-width:560px;background:${COLORS.card};border:1px solid ${COLORS.border};">
<!-- black logo band -->
<tr>
<td align="center" bgcolor="${COLORS.band}" style="background:${COLORS.band};padding:36px 32px 30px;">
<img src="${LOGO_EMAIL_URL}" alt="${STUDIO_NAME}" width="240" style="display:block;width:240px;max-width:72%;height:auto;border:0;" />
</td>
</tr>
<!-- gold accent rule -->
<tr>
<td bgcolor="${COLORS.rule}" style="background:${COLORS.rule};height:3px;line-height:3px;font-size:0;">&nbsp;</td>
</tr>
<!-- light body -->
${innerHtml}
<!-- black footer band -->
<tr>
<td bgcolor="${COLORS.band}" style="background:${COLORS.band};padding:24px 32px;">
<div style="font-family:${DISPLAY_FONT};font-size:14px;letter-spacing:3px;color:${COLORS.accentOnDark};text-transform:uppercase;">${STUDIO_NAME}</div>
<div style="font-family:${SANS_FONT};font-size:12px;color:#8f867a;margin-top:4px;">${TAGLINE}</div>
<div style="font-family:${SANS_FONT};font-size:11px;color:#6f6a5f;margin-top:12px;line-height:1.6;">Sent automatically from the studio website.</div>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>`;
}
// Sent to the studio staff with the submitted message.
export function staffNotificationEmail(input: ContactInput): {
subject: string;
html: string;
text: string;
} {
const { name, email, subject, message } = input;
const row = (label: string, value: string) => `
<tr>
<td style="padding:0 0 14px;">
<div style="font-family:${DISPLAY_FONT};font-size:11px;letter-spacing:3px;color:${COLORS.accent};text-transform:uppercase;">${label}</div>
<div style="font-family:${SANS_FONT};font-size:15px;color:${COLORS.ink};margin-top:4px;">${value}</div>
</td>
</tr>`;
const inner = `
<tr>
<td bgcolor="${COLORS.card}" style="background:${COLORS.card};padding:36px 32px;">
${eyebrow("New message")}
${heading("Someone reached out")}
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin-top:28px;">
${row("From", `${escapeHtml(name)} &lt;${escapeHtml(email)}&gt;`)}
${row("Subject", escapeHtml(subject))}
</table>
<div style="font-family:${DISPLAY_FONT};font-size:11px;letter-spacing:3px;color:${COLORS.accent};text-transform:uppercase;margin:8px 0 10px;">Message</div>
<div style="font-family:${SANS_FONT};font-size:15px;color:${COLORS.ink};line-height:1.7;background:${COLORS.box};border:1px solid ${COLORS.border};padding:18px;">${nl2br(message)}</div>
<!-- reply button (filled black, gold text) -->
<table role="presentation" cellpadding="0" cellspacing="0" style="margin-top:28px;">
<tr>
<td bgcolor="${COLORS.band}" style="background:${COLORS.band};">
<a href="mailto:${encodeURIComponent(email)}?subject=${encodeURIComponent("Re: " + subject)}"
style="display:inline-block;font-family:${DISPLAY_FONT};font-size:14px;letter-spacing:3px;text-transform:uppercase;color:${COLORS.accentOnDark};text-decoration:none;padding:14px 30px;">
Reply to ${escapeHtml(name)} &rarr;
</a>
</td>
</tr>
</table>
</td>
</tr>`;
return {
subject: `[Contact] ${subject}`,
html: shell(inner),
text: `New message for ${STUDIO_NAME}\n\nFrom: ${name} <${email}>\nSubject: ${subject}\n\n${message}`,
};
}
// Auto-reply sent to the person who submitted the form.
export function autoReplyEmail(input: ContactInput): {
subject: string;
html: string;
text: string;
} {
const { name, message } = input;
const firstName = name.split(/\s+/)[0] || name;
const inner = `
<tr>
<td bgcolor="${COLORS.card}" style="background:${COLORS.card};padding:36px 32px;">
${eyebrow("Message received")}
${heading("Thanks for<br />reaching out")}
<div style="font-family:${SANS_FONT};font-size:15px;color:${COLORS.ink};line-height:1.8;margin-top:26px;">
<p style="margin:0 0 16px;">Hi ${escapeHtml(firstName)},</p>
<p style="margin:0 0 16px;">Thanks for getting in touch with ${STUDIO_NAME}. We've received your message and a real human will get back to you soon.</p>
<p style="margin:0 0 10px;color:${COLORS.inkSoft};font-size:13px;">For reference, here's what you sent us:</p>
</div>
<div style="font-family:${SANS_FONT};font-size:14px;color:${COLORS.inkSoft};line-height:1.7;background:${COLORS.box};border:1px solid ${COLORS.border};padding:18px;">${nl2br(message)}</div>
<div style="font-family:${DISPLAY_FONT};font-size:18px;letter-spacing:2px;color:${COLORS.accent};text-transform:uppercase;margin-top:30px;">The ${STUDIO_NAME} team</div>
</td>
</tr>`;
return {
subject: `We got your message`,
html: shell(inner),
text: `Hi ${firstName},\n\nThanks for getting in touch with ${STUDIO_NAME}. We've received your message and a real human will get back to you soon.\n\nFor reference, here's what you sent us:\n\n${message}\n\nThe ${STUDIO_NAME} team`,
};
}