@vennyx/mila-react-template 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,55 @@
1
+ import type * as React from 'react';
2
+ export interface MilaButtonFixedProps {
3
+ href: string;
4
+ label: string;
5
+ /** px. Default 240 -- comfortable for a single short CTA phrase in
6
+ * either locale; pass a wider value for longer Turkish labels rather
7
+ * than switching to MilaButtonFluid, when Outlook rounded corners still
8
+ * matter for that specific template. */
9
+ width?: number;
10
+ /** px. Default 48. */
11
+ height?: number;
12
+ color?: string;
13
+ textColor?: string;
14
+ }
15
+ /**
16
+ * A real, VML-rounded-corner, fixed-width bulletproof button for Outlook
17
+ * Desktop (2007-2021+, Word rendering engine). Verified directly from
18
+ * react-email@6.7.0's own source that its built-in `Button` component does
19
+ * NOT emit VML at all (it uses a different, non-VML MSO font-width-spacer
20
+ * padding technique instead -- see MilaButtonFluid below) -- so this
21
+ * component is fully hand-written, not a wrapper around react-email's own
22
+ * Button.
23
+ *
24
+ * Rendered as one `dangerouslySetInnerHTML` string rather than nested JSX:
25
+ * the entire markup is 100% static structure with a handful of interpolated
26
+ * values (href/label/dimensions/colors), and the two "and everyone else"
27
+ * variants (`<!--[if !mso]><!-- -->` wrapping a real `<a>`) fight React's
28
+ * JSX model for no benefit here -- a single literal template string is
29
+ * simpler and more directly inspectable against the exact bulletproof
30
+ * pattern research verified, with zero risk of JSX/comment-escaping
31
+ * surprises.
32
+ *
33
+ * See docs/superpowers/specs/2026-07-10-mila-email-engine-design.md §5 and
34
+ * the research doc's §1 for the exact source pattern this mirrors
35
+ * (Litmus/dev.to/Campaign Monitor -- v:roundrect, not v:rect, since
36
+ * v:roundrect supports the `arcsize` attribute for rounded corners).
37
+ */
38
+ export declare function MilaButtonFixed({ href, label, width, height, color, textColor }: MilaButtonFixedProps): React.JSX.Element;
39
+ export interface MilaButtonFluidProps {
40
+ href: string;
41
+ label: string;
42
+ style?: React.CSSProperties;
43
+ }
44
+ /**
45
+ * A fluid-width button whose padding survives Outlook Desktop via
46
+ * react-email's own built-in MSO font-width-spacer hack (verified directly
47
+ * from its source: `Button` computes `mso-font-width`/`mso-text-raise`
48
+ * conditional-comment spacer `<i>` tags from the `padding` style prop) --
49
+ * no VML, so no rounded corners in Outlook Desktop specifically (square
50
+ * corners there only; every other client gets the real `border-radius`).
51
+ * Use this variant where a bilingual label's length varies too much
52
+ * between locales for a fixed width to look right in both (Turkish CTA
53
+ * strings routinely run longer than their English equivalents).
54
+ */
55
+ export declare function MilaButtonFluid({ href, label, style }: MilaButtonFluidProps): React.JSX.Element;
package/dist/button.js ADDED
@@ -0,0 +1,84 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Button } from 'react-email';
3
+ import { colors, fontStack } from './tokens.js';
4
+ function escapeHtmlText(value) {
5
+ return value
6
+ .replace(/&/g, '&amp;')
7
+ .replace(/</g, '&lt;')
8
+ .replace(/>/g, '&gt;')
9
+ .replace(/"/g, '&quot;')
10
+ .replace(/'/g, '&#39;');
11
+ }
12
+ /**
13
+ * A real, VML-rounded-corner, fixed-width bulletproof button for Outlook
14
+ * Desktop (2007-2021+, Word rendering engine). Verified directly from
15
+ * react-email@6.7.0's own source that its built-in `Button` component does
16
+ * NOT emit VML at all (it uses a different, non-VML MSO font-width-spacer
17
+ * padding technique instead -- see MilaButtonFluid below) -- so this
18
+ * component is fully hand-written, not a wrapper around react-email's own
19
+ * Button.
20
+ *
21
+ * Rendered as one `dangerouslySetInnerHTML` string rather than nested JSX:
22
+ * the entire markup is 100% static structure with a handful of interpolated
23
+ * values (href/label/dimensions/colors), and the two "and everyone else"
24
+ * variants (`<!--[if !mso]><!-- -->` wrapping a real `<a>`) fight React's
25
+ * JSX model for no benefit here -- a single literal template string is
26
+ * simpler and more directly inspectable against the exact bulletproof
27
+ * pattern research verified, with zero risk of JSX/comment-escaping
28
+ * surprises.
29
+ *
30
+ * See docs/superpowers/specs/2026-07-10-mila-email-engine-design.md §5 and
31
+ * the research doc's §1 for the exact source pattern this mirrors
32
+ * (Litmus/dev.to/Campaign Monitor -- v:roundrect, not v:rect, since
33
+ * v:roundrect supports the `arcsize` attribute for rounded corners).
34
+ */
35
+ export function MilaButtonFixed({ href, label, width = 240, height = 48, color = colors.accent, textColor = colors.buttonText }) {
36
+ // Deliberately never wired to `.mila-accent-bg`: a CTA button's brand
37
+ // accent color is a fixed constant in both light and dark mode, not
38
+ // something that should fade into the surrounding background.
39
+ const safeLabel = escapeHtmlText(label);
40
+ const safeHref = escapeHtmlText(href);
41
+ const safeColor = escapeHtmlText(color);
42
+ const safeTextColor = escapeHtmlText(textColor);
43
+ const html = `<table role="presentation" border="0" cellspacing="0" cellpadding="0" align="center">
44
+ <tr>
45
+ <td align="center">
46
+ <!--[if mso]>
47
+ <v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="${safeHref}" style="height:${height}px;v-text-anchor:middle;width:${width}px;" arcsize="12%" fillcolor="${safeColor}" strokecolor="${safeColor}">
48
+ <w:anchorlock/>
49
+ <center style="color:${safeTextColor};font-family:${fontStack.body};font-size:16px;font-weight:bold;">${safeLabel}</center>
50
+ </v:roundrect>
51
+ <![endif]-->
52
+ <!--[if !mso]><!-- -->
53
+ <a href="${safeHref}" target="_blank" role="button" style="background-color:${safeColor};border:1px solid ${safeColor};border-radius:6px;color:${safeTextColor};display:inline-block;font-family:${fontStack.body};font-size:16px;font-weight:bold;line-height:${height}px;text-align:center;text-decoration:none;width:${width}px;-webkit-text-size-adjust:none;mso-hide:all;">${safeLabel}</a>
54
+ <!--<![endif]-->
55
+ </td>
56
+ </tr>
57
+ </table>`;
58
+ return _jsx("div", { dangerouslySetInnerHTML: { __html: html } });
59
+ }
60
+ /**
61
+ * A fluid-width button whose padding survives Outlook Desktop via
62
+ * react-email's own built-in MSO font-width-spacer hack (verified directly
63
+ * from its source: `Button` computes `mso-font-width`/`mso-text-raise`
64
+ * conditional-comment spacer `<i>` tags from the `padding` style prop) --
65
+ * no VML, so no rounded corners in Outlook Desktop specifically (square
66
+ * corners there only; every other client gets the real `border-radius`).
67
+ * Use this variant where a bilingual label's length varies too much
68
+ * between locales for a fixed width to look right in both (Turkish CTA
69
+ * strings routinely run longer than their English equivalents).
70
+ */
71
+ export function MilaButtonFluid({ href, label, style }) {
72
+ return (_jsx(Button, { href: href, style: {
73
+ backgroundColor: colors.accent,
74
+ color: colors.buttonText,
75
+ fontFamily: fontStack.body,
76
+ fontSize: 16,
77
+ fontWeight: 'bold',
78
+ textDecoration: 'none',
79
+ borderRadius: 6,
80
+ padding: '14px 24px',
81
+ ...style,
82
+ }, children: label }));
83
+ }
84
+ //# sourceMappingURL=button.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"button.js","sourceRoot":"","sources":["../src/button.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEhD,SAAS,cAAc,CAAC,KAAa;IACnC,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC5B,CAAC;AAgBD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,eAAe,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,GAAG,GAAG,EAAE,MAAM,GAAG,EAAE,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAAC,UAAU,EAAwB;IACnJ,sEAAsE;IACtE,oEAAoE;IACpE,8DAA8D;IAC9D,MAAM,SAAS,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,SAAS,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,aAAa,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC;IAChD,MAAM,IAAI,GAAG;;;;mHAIoG,QAAQ,mBAAmB,MAAM,iCAAiC,KAAK,iCAAiC,SAAS,kBAAkB,SAAS;;+BAEhO,aAAa,gBAAgB,SAAS,CAAC,IAAI,sCAAsC,SAAS;;;;iBAIxG,QAAQ,2DAA2D,SAAS,qBAAqB,SAAS,4BAA4B,aAAa,qCAAqC,SAAS,CAAC,IAAI,gDAAgD,MAAM,mDAAmD,KAAK,mDAAmD,SAAS;;;;SAIxX,CAAC;IACR,OAAO,cAAK,uBAAuB,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,GAAI,CAAC;AAC5D,CAAC;AAQD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAwB;IAC1E,OAAO,CACL,KAAC,MAAM,IACL,IAAI,EAAE,IAAI,EACV,KAAK,EAAE;YACL,eAAe,EAAE,MAAM,CAAC,MAAM;YAC9B,KAAK,EAAE,MAAM,CAAC,UAAU;YACxB,UAAU,EAAE,SAAS,CAAC,IAAI;YAC1B,QAAQ,EAAE,EAAE;YACZ,UAAU,EAAE,MAAM;YAClB,cAAc,EAAE,MAAM;YACtB,YAAY,EAAE,CAAC;YACf,OAAO,EAAE,WAAW;YACpB,GAAG,KAAK;SACT,YAEA,KAAK,GACC,CACV,CAAC;AACJ,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type * as React from 'react';
2
+ export interface MilaDividerProps {
3
+ style?: React.CSSProperties;
4
+ }
5
+ export declare function MilaDivider({ style }: MilaDividerProps): React.JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Hr } from 'react-email';
3
+ import { colors } from './tokens.js';
4
+ export function MilaDivider({ style }) {
5
+ return _jsx(Hr, { className: "mila-border", style: { borderColor: colors.border, margin: '24px 0', ...style } });
6
+ }
7
+ //# sourceMappingURL=divider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"divider.js","sourceRoot":"","sources":["../src/divider.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAMrC,MAAM,UAAU,WAAW,CAAC,EAAE,KAAK,EAAoB;IACrD,OAAO,KAAC,EAAE,IAAC,SAAS,EAAC,aAAa,EAAC,KAAK,EAAE,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,GAAI,CAAC;AAC3G,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The only footer variant shipped in v1 -- deliberately excludes a postal
3
+ * address and an unsubscribe link. Neither is legally required for
4
+ * transactional mail (CAN-SPAM's own transactional-message exemption names
5
+ * password resets/receipts/OTPs explicitly; GDPR mandates no specific
6
+ * footer content at all), and an unsubscribe link on a password-reset/
7
+ * account-security email is itself a UX/security anti-pattern -- it
8
+ * implies account-security mail is something a user can opt out of. A
9
+ * distinct `MilaMarketingFooter` (postal address, unsubscribe, İYS
10
+ * consent-hook for Turkish recipients) is explicit future scope, gated
11
+ * behind whichever future phase adds bulk/marketing send capability --
12
+ * not this one. See
13
+ * docs/superpowers/specs/2026-07-10-mila-email-engine-design.md §6.3/§11.
14
+ */
15
+ export declare function MilaFooter(): import("react").JSX.Element;
package/dist/footer.js ADDED
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { colors, fontStack, spacing } from './tokens.js';
3
+ /**
4
+ * The only footer variant shipped in v1 -- deliberately excludes a postal
5
+ * address and an unsubscribe link. Neither is legally required for
6
+ * transactional mail (CAN-SPAM's own transactional-message exemption names
7
+ * password resets/receipts/OTPs explicitly; GDPR mandates no specific
8
+ * footer content at all), and an unsubscribe link on a password-reset/
9
+ * account-security email is itself a UX/security anti-pattern -- it
10
+ * implies account-security mail is something a user can opt out of. A
11
+ * distinct `MilaMarketingFooter` (postal address, unsubscribe, İYS
12
+ * consent-hook for Turkish recipients) is explicit future scope, gated
13
+ * behind whichever future phase adds bulk/marketing send capability --
14
+ * not this one. See
15
+ * docs/superpowers/specs/2026-07-10-mila-email-engine-design.md §6.3/§11.
16
+ */
17
+ export function MilaFooter() {
18
+ return (_jsxs("p", { className: "mila-text-muted mila-border", style: {
19
+ margin: 0,
20
+ fontFamily: fontStack.body,
21
+ fontSize: 12,
22
+ lineHeight: '18px',
23
+ color: colors.textMuted,
24
+ paddingTop: spacing.lg,
25
+ borderTop: `1px solid ${colors.border}`,
26
+ }, children: ["VENNYX YAZILIM DANI\u015EMANLIK A.\u015E. \u00B7", ' ', _jsx("a", { href: "mailto:support@mila.cx", className: "mila-text-muted", style: { color: colors.textMuted }, children: "support@mila.cx" })] }));
27
+ }
28
+ //# sourceMappingURL=footer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"footer.js","sourceRoot":"","sources":["../src/footer.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEzD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,CACL,aACE,SAAS,EAAC,6BAA6B,EACvC,KAAK,EAAE;YACL,MAAM,EAAE,CAAC;YACT,UAAU,EAAE,SAAS,CAAC,IAAI;YAC1B,QAAQ,EAAE,EAAE;YACZ,UAAU,EAAE,MAAM;YAClB,KAAK,EAAE,MAAM,CAAC,SAAS;YACvB,UAAU,EAAE,OAAO,CAAC,EAAE;YACtB,SAAS,EAAE,aAAa,MAAM,CAAC,MAAM,EAAE;SACxC,iEAEwC,GAAG,EAC5C,YAAG,IAAI,EAAC,wBAAwB,EAAC,SAAS,EAAC,iBAAiB,EAAC,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,gCAE3F,IACF,CACL,CAAC;AACJ,CAAC"}
package/dist/head.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ export interface MilaHeadProps {
2
+ title?: string;
3
+ }
4
+ /**
5
+ * Every head-level requirement from
6
+ * docs/superpowers/specs/2026-07-10-mila-email-engine-design.md §5/§6.2 in
7
+ * one component, so no template can accidentally omit one. `charSet` is
8
+ * written using React's own special camelCase prop (NOT a literal
9
+ * `charset`-keyed spread) deliberately: confirmed empirically (rendering
10
+ * this component against the actual installed `react-email@6.7.0` /
11
+ * `react@19.2.7`) that React 19's real server renderer treats `<meta
12
+ * charSet>` and `<meta name="viewport">` as special "Hoistable" resources
13
+ * and unconditionally moves them to the very front of `<head>` -- ahead of
14
+ * every other child, and ahead of react-email's own `<Head>` wrapper's
15
+ * unconditionally-injected `Content-Type`/`x-apple-disable-message-
16
+ * reformatting` metas -- which is exactly the "first 1024 bytes" placement
17
+ * this component needs. Writing `charSet` as a plain string-keyed attribute
18
+ * instead renders correctly cosmetically (lowercase `charset=`) but silently
19
+ * opts OUT of that hoisting, since React's special-case only keys off the
20
+ * literal `charSet` prop name, which would push it several tags later in the
21
+ * document -- functionally worse despite looking more "correct" as source.
22
+ * The resulting HTML attribute name is emitted as `charSet` (capital S) --
23
+ * not a bug: HTML attribute names are case-insensitive to every conforming
24
+ * parser (including every mail client's), so `charSet="utf-8"` and
25
+ * `charset="utf-8"` are identical once parsed.
26
+ */
27
+ export declare function MilaHead({ title }: MilaHeadProps): import("react").JSX.Element;
package/dist/head.js ADDED
@@ -0,0 +1,58 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Head } from 'react-email';
3
+ import { colors } from './tokens.js';
4
+ const DARK_MODE_CSS = `
5
+ :root { color-scheme: light dark; supported-color-schemes: light dark; }
6
+ @media (prefers-color-scheme: dark) {
7
+ .mila-bg { background-color: ${colors.backgroundDark} !important; }
8
+ .mila-surface { background-color: ${colors.surfaceDark} !important; }
9
+ .mila-text { color: ${colors.textDark} !important; }
10
+ .mila-text-muted { color: ${colors.textMutedDark} !important; }
11
+ .mila-border { border-color: ${colors.borderDark} !important; }
12
+ .mila-accent-bg { background-color: ${colors.accentDark} !important; }
13
+ }
14
+ `;
15
+ /**
16
+ * The Outlook DPI-correction block MUST be a raw `<!--[if mso]>...` HTML
17
+ * comment directly inside <head> -- JSX has no way to emit a literal,
18
+ * unescaped HTML comment as a sibling node (`{/* ... *\/}` is a JS comment,
19
+ * stripped entirely, not rendered). The pragmatic, widely-used technique
20
+ * (matching how react-email's own Button/Preview components inject raw
21
+ * MSO-only markup internally, confirmed directly from their source) is
22
+ * `dangerouslySetInnerHTML` on a wrapper -- email-rendering HTML parsers
23
+ * (Outlook's Word engine, every webmail sanitizer) are exceptionally
24
+ * lenient about non-standard head content, and this exact div-wrapped
25
+ * pattern is what real bulletproof-email templates use in production.
26
+ */
27
+ function MsoOfficeDocumentSettings() {
28
+ return (_jsx("div", { dangerouslySetInnerHTML: {
29
+ __html: '<!--[if mso]><noscript><xml><o:OfficeDocumentSettings><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml></noscript><![endif]-->',
30
+ } }));
31
+ }
32
+ /**
33
+ * Every head-level requirement from
34
+ * docs/superpowers/specs/2026-07-10-mila-email-engine-design.md §5/§6.2 in
35
+ * one component, so no template can accidentally omit one. `charSet` is
36
+ * written using React's own special camelCase prop (NOT a literal
37
+ * `charset`-keyed spread) deliberately: confirmed empirically (rendering
38
+ * this component against the actual installed `react-email@6.7.0` /
39
+ * `react@19.2.7`) that React 19's real server renderer treats `<meta
40
+ * charSet>` and `<meta name="viewport">` as special "Hoistable" resources
41
+ * and unconditionally moves them to the very front of `<head>` -- ahead of
42
+ * every other child, and ahead of react-email's own `<Head>` wrapper's
43
+ * unconditionally-injected `Content-Type`/`x-apple-disable-message-
44
+ * reformatting` metas -- which is exactly the "first 1024 bytes" placement
45
+ * this component needs. Writing `charSet` as a plain string-keyed attribute
46
+ * instead renders correctly cosmetically (lowercase `charset=`) but silently
47
+ * opts OUT of that hoisting, since React's special-case only keys off the
48
+ * literal `charSet` prop name, which would push it several tags later in the
49
+ * document -- functionally worse despite looking more "correct" as source.
50
+ * The resulting HTML attribute name is emitted as `charSet` (capital S) --
51
+ * not a bug: HTML attribute names are case-insensitive to every conforming
52
+ * parser (including every mail client's), so `charSet="utf-8"` and
53
+ * `charset="utf-8"` are identical once parsed.
54
+ */
55
+ export function MilaHead({ title }) {
56
+ return (_jsxs(Head, { children: [_jsx("meta", { charSet: "utf-8" }), _jsx("meta", { httpEquiv: "X-UA-Compatible", content: "IE=edge" }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1, user-scalable=yes" }), _jsx("meta", { name: "format-detection", content: "telephone=no, date=no, address=no, email=no, url=no" }), _jsx("meta", { name: "x-apple-disable-message-reformatting", content: "" }), _jsx("meta", { name: "color-scheme", content: "light dark" }), _jsx("meta", { name: "supported-color-schemes", content: "light dark" }), title ? _jsx("title", { children: title }) : null, _jsx(MsoOfficeDocumentSettings, {}), _jsx("style", { dangerouslySetInnerHTML: { __html: DARK_MODE_CSS } })] }));
57
+ }
58
+ //# sourceMappingURL=head.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"head.js","sourceRoot":"","sources":["../src/head.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAMrC,MAAM,aAAa,GAAG;;;qCAGe,MAAM,CAAC,cAAc;0CAChB,MAAM,CAAC,WAAW;4BAChC,MAAM,CAAC,QAAQ;kCACT,MAAM,CAAC,aAAa;qCACjB,MAAM,CAAC,UAAU;4CACV,MAAM,CAAC,UAAU;;GAE1D,CAAC;AAEJ;;;;;;;;;;;GAWG;AACH,SAAS,yBAAyB;IAChC,OAAO,CACL,cACE,uBAAuB,EAAE;YACvB,MAAM,EACJ,qJAAqJ;SACxJ,GACD,CACH,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,QAAQ,CAAC,EAAE,KAAK,EAAiB;IAC/C,OAAO,CACL,MAAC,IAAI,eACH,eAAM,OAAO,EAAC,OAAO,GAAG,EACxB,eAAM,SAAS,EAAC,iBAAiB,EAAC,OAAO,EAAC,SAAS,GAAG,EACtD,eAAM,IAAI,EAAC,UAAU,EAAC,OAAO,EAAC,wDAAwD,GAAG,EACzF,eAAM,IAAI,EAAC,kBAAkB,EAAC,OAAO,EAAC,qDAAqD,GAAG,EAC9F,eAAM,IAAI,EAAC,sCAAsC,EAAC,OAAO,EAAC,EAAE,GAAG,EAC/D,eAAM,IAAI,EAAC,cAAc,EAAC,OAAO,EAAC,YAAY,GAAG,EACjD,eAAM,IAAI,EAAC,yBAAyB,EAAC,OAAO,EAAC,YAAY,GAAG,EAC3D,KAAK,CAAC,CAAC,CAAC,0BAAQ,KAAK,GAAS,CAAC,CAAC,CAAC,IAAI,EACtC,KAAC,yBAAyB,KAAG,EAC7B,gBAAO,uBAAuB,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,GAAI,IACxD,CACR,CAAC;AACJ,CAAC"}
package/dist/html.d.ts ADDED
@@ -0,0 +1,32 @@
1
+ import type * as React from 'react';
2
+ /** The two locales mila's email templates support. Deliberately NOT
3
+ * imported from `@mila/shared`'s own `AppLocale` type: `@vennyx/mila-react-template`
4
+ * is a PUBLISHED npm package, `@mila/shared` is `"private": true` and never
5
+ * published -- a public package's emitted `.d.ts` referencing an
6
+ * unpublished workspace type would break for any external installer. This
7
+ * is intentionally a small, self-contained duplicate. */
8
+ export type MilaLocale = 'tr' | 'en';
9
+ export interface MilaHtmlProps {
10
+ locale: MilaLocale;
11
+ children: React.ReactNode;
12
+ }
13
+ /**
14
+ * Wraps react-email's own `Html` and sets every `lang` signal research
15
+ * confirmed is independently load-bearing: `lang` (universal), a literal
16
+ * `xml:lang` (Outlook Desktop is documented to prioritize this namespaced
17
+ * form), and MilaBody below duplicates `lang` again on `<body>` since
18
+ * Yahoo/AOL are documented to strip the `<html>` element itself in some
19
+ * render paths, taking `lang` with it. `dir="ltr"` is hardcoded: mila has
20
+ * no RTL-language templates.
21
+ *
22
+ * React recognizes `xmlLang` as a special-cased camelCase prop that
23
+ * serializes to the literal `xml:lang` HTML attribute with no console
24
+ * warning — this is the correct, idiomatic way to set it.
25
+ */
26
+ export declare function MilaHtml({ locale, children }: MilaHtmlProps): React.JSX.Element;
27
+ export interface MilaBodyProps {
28
+ locale: MilaLocale;
29
+ style?: React.CSSProperties;
30
+ children: React.ReactNode;
31
+ }
32
+ export declare function MilaBody({ locale, style, children }: MilaBodyProps): React.JSX.Element;
package/dist/html.js ADDED
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Body, Html } from 'react-email';
3
+ import { colors } from './tokens.js';
4
+ /**
5
+ * Wraps react-email's own `Html` and sets every `lang` signal research
6
+ * confirmed is independently load-bearing: `lang` (universal), a literal
7
+ * `xml:lang` (Outlook Desktop is documented to prioritize this namespaced
8
+ * form), and MilaBody below duplicates `lang` again on `<body>` since
9
+ * Yahoo/AOL are documented to strip the `<html>` element itself in some
10
+ * render paths, taking `lang` with it. `dir="ltr"` is hardcoded: mila has
11
+ * no RTL-language templates.
12
+ *
13
+ * React recognizes `xmlLang` as a special-cased camelCase prop that
14
+ * serializes to the literal `xml:lang` HTML attribute with no console
15
+ * warning — this is the correct, idiomatic way to set it.
16
+ */
17
+ export function MilaHtml({ locale, children }) {
18
+ return (_jsx(Html, { lang: locale, dir: "ltr", ...{ xmlLang: locale }, children: children }));
19
+ }
20
+ export function MilaBody({ locale, style, children }) {
21
+ return (_jsx(Body, { lang: locale, className: "mila-bg mila-text", style: {
22
+ margin: 0,
23
+ padding: 0,
24
+ backgroundColor: colors.background,
25
+ color: colors.text,
26
+ ...style,
27
+ }, children: children }));
28
+ }
29
+ //# sourceMappingURL=html.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.js","sourceRoot":"","sources":["../src/html.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAerC;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,QAAQ,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAiB;IAC1D,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAC,KAAK,KAAM,EAAE,OAAO,EAAE,MAAM,EAAU,YAC3D,QAAQ,GACJ,CACR,CAAC;AACJ,CAAC;AAQD,MAAM,UAAU,QAAQ,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAiB;IACjE,OAAO,CACL,KAAC,IAAI,IACH,IAAI,EAAE,MAAM,EACZ,SAAS,EAAC,mBAAmB,EAC7B,KAAK,EAAE;YACL,MAAM,EAAE,CAAC;YACT,OAAO,EAAE,CAAC;YACV,eAAe,EAAE,MAAM,CAAC,UAAU;YAClC,KAAK,EAAE,MAAM,CAAC,IAAI;YAClB,GAAG,KAAK;SACT,YAEA,QAAQ,GACJ,CACR,CAAC;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ export * from './tokens.js';
2
+ export * from './html.js';
3
+ export * from './head.js';
4
+ export * from './logo.js';
5
+ export * from './layout.js';
6
+ export * from './button.js';
7
+ export * from './divider.js';
8
+ export * from './otp-code.js';
9
+ export * from './footer.js';
10
+ export * from './render.js';
11
+ export * from './lint.js';
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ export * from './tokens.js';
2
+ export * from './html.js';
3
+ export * from './head.js';
4
+ export * from './logo.js';
5
+ export * from './layout.js';
6
+ export * from './button.js';
7
+ export * from './divider.js';
8
+ export * from './otp-code.js';
9
+ export * from './footer.js';
10
+ export * from './render.js';
11
+ export * from './lint.js';
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type * as React from 'react';
2
+ import { type MilaLocale } from './html.js';
3
+ export interface MilaEmailLayoutProps {
4
+ locale: MilaLocale;
5
+ /** The inbox-list preview snippet (subject-adjacent text). Kept under
6
+ * ~150 chars by convention (client-specific budgets vary and are
7
+ * unreliable, see the research doc's §8) -- the Preview component pads
8
+ * the rest with invisible characters automatically. */
9
+ previewText: string;
10
+ /** Optional <title> -- most clients ignore it, included for completeness. */
11
+ title?: string;
12
+ children: React.ReactNode;
13
+ }
14
+ /**
15
+ * The hybrid ("spongy") responsive skeleton every template renders exactly
16
+ * once. Outlook Desktop (Word engine) ignores CSS max-width entirely, so it
17
+ * gets a real, separate, fixed-600px HTML table via `<!--[if mso]>`; every
18
+ * other client renders a fluid `width="100%" style="max-width:600px"`
19
+ * table that shrinks on small screens. See
20
+ * docs/superpowers/specs/2026-07-10-mila-email-engine-design.md §5 and the
21
+ * research doc's §4 for the exact pattern this mirrors.
22
+ */
23
+ export declare function MilaEmailLayout({ locale, previewText, title, children }: MilaEmailLayoutProps): React.JSX.Element;
package/dist/layout.js ADDED
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Container, Preview } from 'react-email';
3
+ import { MilaBody, MilaHtml } from './html.js';
4
+ import { MilaHead } from './head.js';
5
+ import { MilaLogo } from './logo.js';
6
+ import { MilaFooter } from './footer.js';
7
+ import { containerWidth, spacing } from './tokens.js';
8
+ /**
9
+ * The hybrid ("spongy") responsive skeleton every template renders exactly
10
+ * once. Outlook Desktop (Word engine) ignores CSS max-width entirely, so it
11
+ * gets a real, separate, fixed-600px HTML table via `<!--[if mso]>`; every
12
+ * other client renders a fluid `width="100%" style="max-width:600px"`
13
+ * table that shrinks on small screens. See
14
+ * docs/superpowers/specs/2026-07-10-mila-email-engine-design.md §5 and the
15
+ * research doc's §4 for the exact pattern this mirrors.
16
+ */
17
+ export function MilaEmailLayout({ locale, previewText, title, children }) {
18
+ return (_jsxs(MilaHtml, { locale: locale, children: [_jsx(MilaHead, { title: title }), _jsx(Preview, { children: previewText }), _jsxs(MilaBody, { locale: locale, children: [_jsx("div", { dangerouslySetInnerHTML: {
19
+ __html: `<!--[if mso]>
20
+ <table align="center" border="0" cellspacing="0" cellpadding="0" width="${containerWidth}">
21
+ <tr>
22
+ <td align="center" valign="top" width="${containerWidth}">
23
+ <![endif]-->`,
24
+ } }), _jsx(Container, { style: { maxWidth: containerWidth, width: '100%', padding: `${spacing.md}px ${spacing.sm}px` }, children: _jsx("table", { role: "presentation", width: "100%", border: 0, cellPadding: "0", cellSpacing: "0", children: _jsxs("tbody", { children: [_jsx("tr", { children: _jsx("td", { style: { paddingBottom: spacing.md }, children: _jsx(MilaLogo, { href: "https://mila.cx", alt: "mila anasayfa / homepage" }) }) }), _jsx("tr", { children: _jsx("td", { children: children }) }), _jsx("tr", { children: _jsx("td", { children: _jsx(MilaFooter, {}) }) })] }) }) }), _jsx("div", { dangerouslySetInnerHTML: { __html: `<!--[if mso]>\n</td>\n</tr>\n</table>\n<![endif]-->` } })] })] }));
25
+ }
26
+ //# sourceMappingURL=layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.js","sourceRoot":"","sources":["../src/layout.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAmB,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AActD;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAwB;IAC5F,OAAO,CACL,MAAC,QAAQ,IAAC,MAAM,EAAE,MAAM,aACtB,KAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,GAAI,EAC1B,KAAC,OAAO,cAAE,WAAW,GAAW,EAChC,MAAC,QAAQ,IAAC,MAAM,EAAE,MAAM,aACtB,cACE,uBAAuB,EAAE;4BACvB,MAAM,EAAE;0EACsD,cAAc;;yCAE/C,cAAc;aAC1C;yBACF,GACD,EACF,KAAC,SAAS,IAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,EAAE,IAAI,EAAE,YACvG,gBAAO,IAAI,EAAC,cAAc,EAAC,KAAK,EAAC,MAAM,EAAC,MAAM,EAAE,CAAC,EAAE,WAAW,EAAC,GAAG,EAAC,WAAW,EAAC,GAAG,YAChF,4BACE,uBACE,aAAI,KAAK,EAAE,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,EAAE,YACtC,KAAC,QAAQ,IAAC,IAAI,EAAC,iBAAiB,EAAC,GAAG,EAAC,0BAA0B,GAAG,GAC/D,GACF,EACL,uBACE,uBAAK,QAAQ,GAAM,GAChB,EACL,uBACE,uBACE,KAAC,UAAU,KAAG,GACX,GACF,IACC,GACF,GACE,EACZ,cAAK,uBAAuB,EAAE,EAAE,MAAM,EAAE,qDAAqD,EAAE,GAAI,IAC1F,IACF,CACZ,CAAC;AACJ,CAAC"}
package/dist/lint.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * A small, self-contained static-assertion suite against a template's
3
+ * final rendered HTML string -- NOT a full caniemail-backed compatibility
4
+ * linter (react-email's own approach, per research pass 3): proportionate
5
+ * to mila's six-template internal library rather than a general-purpose
6
+ * multi-client tool. Checks every hard rule from
7
+ * docs/superpowers/specs/2026-07-10-mila-email-engine-design.md §5/§10.
8
+ * Returns an array of human-readable violation strings; an empty array
9
+ * means the template is compliant.
10
+ */
11
+ export declare function assertMilaEmailCompliant(html: string): string[];
package/dist/lint.js ADDED
@@ -0,0 +1,58 @@
1
+ const SIZE_CEILING_BYTES = 40_000;
2
+ /**
3
+ * A small, self-contained static-assertion suite against a template's
4
+ * final rendered HTML string -- NOT a full caniemail-backed compatibility
5
+ * linter (react-email's own approach, per research pass 3): proportionate
6
+ * to mila's six-template internal library rather than a general-purpose
7
+ * multi-client tool. Checks every hard rule from
8
+ * docs/superpowers/specs/2026-07-10-mila-email-engine-design.md §5/§10.
9
+ * Returns an array of human-readable violation strings; an empty array
10
+ * means the template is compliant.
11
+ */
12
+ export function assertMilaEmailCompliant(html) {
13
+ const violations = [];
14
+ const headMatch = html.match(/<head[^>]*>([\s\S]*?)<\/head>/i);
15
+ const headContent = headMatch?.[1] ?? '';
16
+ const first200OfHead = headContent.slice(0, 200);
17
+ if (!/charset="utf-8"/i.test(first200OfHead)) {
18
+ violations.push('Missing <meta charset="utf-8"> as an early child of <head> (must be within the first ~1024 bytes).');
19
+ }
20
+ if (!/name="color-scheme"/i.test(headContent)) {
21
+ violations.push('Missing <meta name="color-scheme" content="light dark">.');
22
+ }
23
+ if (!/name="supported-color-schemes"/i.test(headContent)) {
24
+ violations.push('Missing <meta name="supported-color-schemes" content="light dark">.');
25
+ }
26
+ const htmlTagMatch = html.match(/<html\b[^>]*>/i);
27
+ const htmlOpenTag = htmlTagMatch?.[0] ?? '';
28
+ if (!/(?:^|\s)lang="[a-z]{2}"/i.test(htmlOpenTag)) {
29
+ violations.push('Missing lang="<locale>" on the <html> element.');
30
+ }
31
+ if (!/xml:lang="[a-z]{2}"/i.test(htmlOpenTag)) {
32
+ violations.push('Missing xml:lang="<locale>" on the <html> element.');
33
+ }
34
+ const imgTags = html.match(/<img\b[^>]*>/gi) ?? [];
35
+ for (const img of imgTags) {
36
+ if (!/\balt="/.test(img)) {
37
+ violations.push(`<img> missing an alt attribute: ${img.slice(0, 80)}`);
38
+ }
39
+ if (/\bsrc="data:/.test(img)) {
40
+ violations.push(`<img> uses a base64 data: URI (never allowed -- always externally-hosted): ${img.slice(0, 80)}`);
41
+ }
42
+ }
43
+ const presentationTables = html.match(/<table\b[^>]*role="presentation"[^>]*>/gi) ?? [];
44
+ for (const table of presentationTables) {
45
+ const hasBorder = /\bborder="0"/i.test(table);
46
+ const hasCellPadding = /\bcellpadding="0"/i.test(table);
47
+ const hasCellSpacing = /\bcellspacing="0"/i.test(table);
48
+ if (!hasBorder || !hasCellPadding || !hasCellSpacing) {
49
+ violations.push(`<table role="presentation"> missing a legacy border="0"/cellpadding="0"/cellspacing="0" attribute: ${table.slice(0, 100)}`);
50
+ }
51
+ }
52
+ const byteSize = Buffer.byteLength(html, 'utf8');
53
+ if (byteSize > SIZE_CEILING_BYTES) {
54
+ violations.push(`Rendered HTML is ${byteSize} bytes, exceeding the 40KB (${SIZE_CEILING_BYTES} byte) size ceiling.`);
55
+ }
56
+ return violations;
57
+ }
58
+ //# sourceMappingURL=lint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lint.js","sourceRoot":"","sources":["../src/lint.ts"],"names":[],"mappings":"AAAA,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC;;;;;;;;;GASG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAAY;IACnD,MAAM,UAAU,GAAa,EAAE,CAAC;IAEhC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAC;IAC/D,MAAM,WAAW,GAAG,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACzC,MAAM,cAAc,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACjD,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC;QAC7C,UAAU,CAAC,IAAI,CAAC,oGAAoG,CAAC,CAAC;IACxH,CAAC;IAED,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QAC9C,UAAU,CAAC,IAAI,CAAC,0DAA0D,CAAC,CAAC;IAC9E,CAAC;IACD,IAAI,CAAC,iCAAiC,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QACzD,UAAU,CAAC,IAAI,CAAC,qEAAqE,CAAC,CAAC;IACzF,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAClD,MAAM,WAAW,GAAG,YAAY,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5C,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QAClD,UAAU,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;IACpE,CAAC;IACD,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QAC9C,UAAU,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC;IACnD,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,UAAU,CAAC,IAAI,CAAC,mCAAmC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;QACzE,CAAC;QACD,IAAI,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7B,UAAU,CAAC,IAAI,CAAC,8EAA8E,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;QACpH,CAAC;IACH,CAAC;IAED,MAAM,kBAAkB,GAAG,IAAI,CAAC,KAAK,CAAC,0CAA0C,CAAC,IAAI,EAAE,CAAC;IACxF,KAAK,MAAM,KAAK,IAAI,kBAAkB,EAAE,CAAC;QACvC,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC9C,MAAM,cAAc,GAAG,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxD,MAAM,cAAc,GAAG,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxD,IAAI,CAAC,SAAS,IAAI,CAAC,cAAc,IAAI,CAAC,cAAc,EAAE,CAAC;YACrD,UAAU,CAAC,IAAI,CAAC,sGAAsG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QAC/I,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACjD,IAAI,QAAQ,GAAG,kBAAkB,EAAE,CAAC;QAClC,UAAU,CAAC,IAAI,CAAC,oBAAoB,QAAQ,+BAA+B,kBAAkB,sBAAsB,CAAC,CAAC;IACvH,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC"}
package/dist/logo.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ export interface MilaLogoProps {
2
+ /** Wraps the mark in a link when provided (e.g. the site homepage). */
3
+ href?: string;
4
+ /** Descriptive alt text. Defaults to "mila" -- override with a fuller
5
+ * destination description (e.g. "mila homepage") when `href` is set,
6
+ * since a linked image needs to describe its destination/action for
7
+ * screen-reader users, not just be decorative (never alt=""). */
8
+ alt?: string;
9
+ }
10
+ /**
11
+ * mila's email header mark: the dove icon (a single retina PNG asset --
12
+ * see scripts/generate-email-logo.mjs; inline SVG is unreliable in email,
13
+ * see this file's own git history/plan) plus the real "mila" wordmark as
14
+ * live HTML text (Inter 600, -0.03em letter-spacing, matching the site's
15
+ * own MilaLogo component's wordmark treatment) -- never baked into the
16
+ * image, both for accessibility (screen readers, research §11) and so it
17
+ * stays crisp/never blurry regardless of zoom level.
18
+ *
19
+ * Sits on an explicit near-white background plate (`colors.background`,
20
+ * #FEFEFE) rather than shipping a second dark-mode-colored logo variant --
21
+ * the mark's own violet gradient has enough contrast against both a light
22
+ * and a dark surface on its own; guaranteeing the PLATE's color (rather
23
+ * than the mark's colors) is the more robust of the two dark-mode-logo
24
+ * techniques research identified, and avoids inventing new brand artwork
25
+ * outside this plan's engineering scope.
26
+ */
27
+ export declare function MilaLogo({ href, alt }: MilaLogoProps): import("react").JSX.Element;
package/dist/logo.js ADDED
@@ -0,0 +1,34 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Img, Link, Section } from 'react-email';
3
+ import { colors, fontStack } from './tokens.js';
4
+ const LOGO_URL = 'https://mila.cx/brand/email/mila-dove-email@2x.png';
5
+ /**
6
+ * mila's email header mark: the dove icon (a single retina PNG asset --
7
+ * see scripts/generate-email-logo.mjs; inline SVG is unreliable in email,
8
+ * see this file's own git history/plan) plus the real "mila" wordmark as
9
+ * live HTML text (Inter 600, -0.03em letter-spacing, matching the site's
10
+ * own MilaLogo component's wordmark treatment) -- never baked into the
11
+ * image, both for accessibility (screen readers, research §11) and so it
12
+ * stays crisp/never blurry regardless of zoom level.
13
+ *
14
+ * Sits on an explicit near-white background plate (`colors.background`,
15
+ * #FEFEFE) rather than shipping a second dark-mode-colored logo variant --
16
+ * the mark's own violet gradient has enough contrast against both a light
17
+ * and a dark surface on its own; guaranteeing the PLATE's color (rather
18
+ * than the mark's colors) is the more robust of the two dark-mode-logo
19
+ * techniques research identified, and avoids inventing new brand artwork
20
+ * outside this plan's engineering scope.
21
+ */
22
+ export function MilaLogo({ href, alt = 'mila' }) {
23
+ const mark = (_jsx(Section, { style: { backgroundColor: colors.background }, children: _jsx("table", { role: "presentation", border: 0, cellPadding: "0", cellSpacing: "0", align: "left", children: _jsx("tbody", { children: _jsxs("tr", { children: [_jsx("td", { style: { paddingRight: 8, verticalAlign: 'middle' }, children: _jsx(Img, { src: LOGO_URL, width: 32, height: 32, alt: alt, style: { display: 'block', border: '0', outline: 'none', textDecoration: 'none' } }) }), _jsx("td", { style: { verticalAlign: 'middle' }, children: _jsx("span", { style: {
24
+ fontFamily: fontStack.heading,
25
+ fontWeight: 600,
26
+ fontSize: 20,
27
+ letterSpacing: '-0.03em',
28
+ color: colors.text,
29
+ }, children: "mila" }) })] }) }) }) }));
30
+ if (!href)
31
+ return mark;
32
+ return (_jsx(Link, { href: href, style: { textDecoration: 'none' }, children: mark }));
33
+ }
34
+ //# sourceMappingURL=logo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logo.js","sourceRoot":"","sources":["../src/logo.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAYhD,MAAM,QAAQ,GAAG,oDAAoD,CAAC;AAEtE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,QAAQ,CAAC,EAAE,IAAI,EAAE,GAAG,GAAG,MAAM,EAAiB;IAC5D,MAAM,IAAI,GAAG,CAKX,KAAC,OAAO,IAAC,KAAK,EAAE,EAAE,eAAe,EAAE,MAAM,CAAC,UAAU,EAAE,YACpD,gBAAO,IAAI,EAAC,cAAc,EAAC,MAAM,EAAE,CAAC,EAAE,WAAW,EAAC,GAAG,EAAC,WAAW,EAAC,GAAG,EAAC,KAAK,EAAC,MAAM,YAChF,0BACE,yBACE,aAAI,KAAK,EAAE,EAAE,YAAY,EAAE,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,YACrD,KAAC,GAAG,IAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,GAAI,GACvI,EACL,aAAI,KAAK,EAAE,EAAE,aAAa,EAAE,QAAQ,EAAE,YACpC,eACE,KAAK,EAAE;oCACL,UAAU,EAAE,SAAS,CAAC,OAAO;oCAC7B,UAAU,EAAE,GAAG;oCACf,QAAQ,EAAE,EAAE;oCACZ,aAAa,EAAE,SAAS;oCACxB,KAAK,EAAE,MAAM,CAAC,IAAI;iCACnB,qBAGI,GACJ,IACF,GACC,GACF,GACA,CACX,CAAC;IAEF,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IAEvB,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,YAChD,IAAI,GACA,CACR,CAAC;AACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ export interface MilaOtpCodeProps {
2
+ code: string;
3
+ }
4
+ /**
5
+ * A monospace, letter-spaced code/OTP display block for invitation and
6
+ * verification flows. Uses the same web-safe monospace stack fallback
7
+ * discipline as every other text element (Outlook Desktop falls back to
8
+ * Times New Roman -- monospace, unlike sans-serif body text, doesn't have
9
+ * an equally universal fallback story, but `ui-monospace` (Apple/Chrome's
10
+ * own system-mono keyword) -> SFMono-Regular -> Menlo -> Consolas ->
11
+ * generic monospace covers every realistic client).
12
+ */
13
+ export declare function MilaOtpCode({ code }: MilaOtpCodeProps): import("react").JSX.Element;
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Section } from 'react-email';
3
+ import { colors, fontStack } from './tokens.js';
4
+ /**
5
+ * A monospace, letter-spaced code/OTP display block for invitation and
6
+ * verification flows. Uses the same web-safe monospace stack fallback
7
+ * discipline as every other text element (Outlook Desktop falls back to
8
+ * Times New Roman -- monospace, unlike sans-serif body text, doesn't have
9
+ * an equally universal fallback story, but `ui-monospace` (Apple/Chrome's
10
+ * own system-mono keyword) -> SFMono-Regular -> Menlo -> Consolas ->
11
+ * generic monospace covers every realistic client).
12
+ */
13
+ export function MilaOtpCode({ code }) {
14
+ return (_jsx(Section, { style: {
15
+ backgroundColor: colors.surface,
16
+ borderRadius: 8,
17
+ padding: '20px 24px',
18
+ textAlign: 'center',
19
+ }, children: _jsx("span", { className: "mila-text", style: {
20
+ fontFamily: fontStack.mono,
21
+ fontSize: 28,
22
+ fontWeight: 600,
23
+ letterSpacing: '0.3em',
24
+ color: colors.text,
25
+ }, children: code }) }));
26
+ }
27
+ //# sourceMappingURL=otp-code.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otp-code.js","sourceRoot":"","sources":["../src/otp-code.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAMhD;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,EAAE,IAAI,EAAoB;IACpD,OAAO,CACL,KAAC,OAAO,IACN,KAAK,EAAE;YACL,eAAe,EAAE,MAAM,CAAC,OAAO;YAC/B,YAAY,EAAE,CAAC;YACf,OAAO,EAAE,WAAW;YACpB,SAAS,EAAE,QAAQ;SACpB,YAED,eACE,SAAS,EAAC,WAAW,EACrB,KAAK,EAAE;gBACL,UAAU,EAAE,SAAS,CAAC,IAAI;gBAC1B,QAAQ,EAAE,EAAE;gBACZ,UAAU,EAAE,GAAG;gBACf,aAAa,EAAE,OAAO;gBACtB,KAAK,EAAE,MAAM,CAAC,IAAI;aACnB,YAEA,IAAI,GACA,GACC,CACX,CAAC;AACJ,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type * as React from 'react';
2
+ /**
3
+ * Thin wrapper over react-email's own `render()`/`toPlainText()` -- both
4
+ * verified directly from react-email@6.7.0's real published types/source
5
+ * (packages/render's `render(node, options?): Promise<string>` and
6
+ * `toPlainText(html, options?): string`, the latter itself wrapping
7
+ * `html-to-text` internally). No separate `html-to-text` dependency is
8
+ * declared in this package's own package.json: react-email already pulls
9
+ * it in transitively and re-exports the wrapper function, so depending on
10
+ * it again directly would just be duplicate version-pinning for no
11
+ * benefit.
12
+ *
13
+ * `hideLinkHrefIfSameAsText` and `img: {format: 'skip'}` match
14
+ * docs/superpowers/specs/2026-07-10-mila-email-engine-design.md §5 rule 14
15
+ * / §6.5 exactly. `wordwrap: 78` is RFC 5322's own recommended soft-wrap
16
+ * width.
17
+ */
18
+ export declare function renderMilaEmail(element: React.ReactElement): Promise<{
19
+ html: string;
20
+ text: string;
21
+ }>;
package/dist/render.js ADDED
@@ -0,0 +1,29 @@
1
+ import { render, toPlainText } from 'react-email';
2
+ /**
3
+ * Thin wrapper over react-email's own `render()`/`toPlainText()` -- both
4
+ * verified directly from react-email@6.7.0's real published types/source
5
+ * (packages/render's `render(node, options?): Promise<string>` and
6
+ * `toPlainText(html, options?): string`, the latter itself wrapping
7
+ * `html-to-text` internally). No separate `html-to-text` dependency is
8
+ * declared in this package's own package.json: react-email already pulls
9
+ * it in transitively and re-exports the wrapper function, so depending on
10
+ * it again directly would just be duplicate version-pinning for no
11
+ * benefit.
12
+ *
13
+ * `hideLinkHrefIfSameAsText` and `img: {format: 'skip'}` match
14
+ * docs/superpowers/specs/2026-07-10-mila-email-engine-design.md §5 rule 14
15
+ * / §6.5 exactly. `wordwrap: 78` is RFC 5322's own recommended soft-wrap
16
+ * width.
17
+ */
18
+ export async function renderMilaEmail(element) {
19
+ const html = await render(element);
20
+ const text = toPlainText(html, {
21
+ wordwrap: 78,
22
+ selectors: [
23
+ { selector: 'a', options: { hideLinkHrefIfSameAsText: true } },
24
+ { selector: 'img', format: 'skip' },
25
+ ],
26
+ });
27
+ return { html, text };
28
+ }
29
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAElD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAA2B;IAC/D,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;IACnC,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,EAAE;QAC7B,QAAQ,EAAE,EAAE;QACZ,SAAS,EAAE;YACT,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,wBAAwB,EAAE,IAAI,EAAE,EAAE;YAC9D,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE;SACpC;KACF,CAAC,CAAC;IACH,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACxB,CAAC"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Design tokens for mila's email templates. Colors are pulled directly from
3
+ * apps/site's/apps/panel's own Chakra `mila` color ramp
4
+ * (apps/site/src/lib/theme.ts) so email stays visually consistent with the
5
+ * rest of the product -- NOT re-derived or approximated.
6
+ *
7
+ * Near-pure (not pure) black/white values are deliberate: Apple Mail is
8
+ * reported (practitioner-observed, not Apple-documented) to auto-invert
9
+ * literal #000000/#FFFFFF more aggressively than off-black/off-white, and
10
+ * off-pure values also improve WCAG contrast headroom regardless of that
11
+ * behavior. See docs/superpowers/specs/2026-07-10-mila-email-engine-design.md
12
+ * §5 rule 9 and §8.
13
+ *
14
+ * The dark-mode neutrals (backgroundDark/surfaceDark/borderDark) are NOT
15
+ * pulled from Chakra's own default dark theme -- Chakra's runtime tokens
16
+ * don't exist at email-render time (email HTML can't load a CSS-in-JS
17
+ * runtime), so these are a deliberate, self-contained, email-only choice: a
18
+ * standard near-black dark surface (#121212, a widely-used dark-UI base),
19
+ * paired with mila.400 (the ramp's own next-lighter step from mila.600) as
20
+ * the dark-mode accent, since a saturated mila.600 is too dark to read
21
+ * clearly on a near-black background.
22
+ */
23
+ export declare const colors: {
24
+ readonly accent: "#7c3aed";
25
+ readonly accentDark: "#a78bfa";
26
+ readonly text: "#0E0E0E";
27
+ readonly textMuted: "#5b5b66";
28
+ readonly textDark: "#FEFEFE";
29
+ readonly textMutedDark: "#a3a3ad";
30
+ readonly background: "#FEFEFE";
31
+ readonly backgroundDark: "#121212";
32
+ readonly surface: "#f5f3ff";
33
+ readonly surfaceDark: "#1f1a2e";
34
+ readonly border: "#ddd6fe";
35
+ readonly borderDark: "#3a3350";
36
+ readonly buttonText: "#FEFEFE";
37
+ };
38
+ /**
39
+ * Full web-safe fallback stack behind mila's Inter brand font. Order
40
+ * matters (docs/superpowers/specs/2026-07-10-mila-email-engine-design.md
41
+ * §5 rule 6): Inter first (only clients that load @font-face pick it up) ->
42
+ * -apple-system/BlinkMacSystemFont (San Francisco on Apple Mail/iOS) ->
43
+ * Segoe UI (effective Outlook Desktop/Windows rendering regardless of CSS)
44
+ * -> Roboto (Gmail's effective default) -> Helvetica/Arial/sans-serif
45
+ * (universal safety net -- guarantees no serif fallback anywhere, since
46
+ * Outlook Desktop falls back all the way to Times New Roman if the whole
47
+ * stack were ever ignored).
48
+ */
49
+ export declare const fontStack: {
50
+ readonly heading: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";
51
+ readonly body: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";
52
+ readonly mono: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace";
53
+ };
54
+ export declare const spacing: {
55
+ readonly xs: 8;
56
+ readonly sm: 16;
57
+ readonly md: 24;
58
+ readonly lg: 32;
59
+ readonly xl: 48;
60
+ };
61
+ /** Matches react-email's own `Container` default (`maxWidth: '37.5em'` ==
62
+ * 600px at a 16px base) -- used by MilaEmailLayout's hybrid Outlook table. */
63
+ export declare const containerWidth = 600;
package/dist/tokens.js ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Design tokens for mila's email templates. Colors are pulled directly from
3
+ * apps/site's/apps/panel's own Chakra `mila` color ramp
4
+ * (apps/site/src/lib/theme.ts) so email stays visually consistent with the
5
+ * rest of the product -- NOT re-derived or approximated.
6
+ *
7
+ * Near-pure (not pure) black/white values are deliberate: Apple Mail is
8
+ * reported (practitioner-observed, not Apple-documented) to auto-invert
9
+ * literal #000000/#FFFFFF more aggressively than off-black/off-white, and
10
+ * off-pure values also improve WCAG contrast headroom regardless of that
11
+ * behavior. See docs/superpowers/specs/2026-07-10-mila-email-engine-design.md
12
+ * §5 rule 9 and §8.
13
+ *
14
+ * The dark-mode neutrals (backgroundDark/surfaceDark/borderDark) are NOT
15
+ * pulled from Chakra's own default dark theme -- Chakra's runtime tokens
16
+ * don't exist at email-render time (email HTML can't load a CSS-in-JS
17
+ * runtime), so these are a deliberate, self-contained, email-only choice: a
18
+ * standard near-black dark surface (#121212, a widely-used dark-UI base),
19
+ * paired with mila.400 (the ramp's own next-lighter step from mila.600) as
20
+ * the dark-mode accent, since a saturated mila.600 is too dark to read
21
+ * clearly on a near-black background.
22
+ */
23
+ export const colors = {
24
+ accent: '#7c3aed', // mila.600
25
+ accentDark: '#a78bfa', // mila.400
26
+ text: '#0E0E0E',
27
+ textMuted: '#5b5b66',
28
+ textDark: '#FEFEFE',
29
+ textMutedDark: '#a3a3ad',
30
+ background: '#FEFEFE',
31
+ backgroundDark: '#121212',
32
+ surface: '#f5f3ff', // mila.50
33
+ surfaceDark: '#1f1a2e',
34
+ border: '#ddd6fe', // mila.200
35
+ borderDark: '#3a3350',
36
+ buttonText: '#FEFEFE',
37
+ };
38
+ /**
39
+ * Full web-safe fallback stack behind mila's Inter brand font. Order
40
+ * matters (docs/superpowers/specs/2026-07-10-mila-email-engine-design.md
41
+ * §5 rule 6): Inter first (only clients that load @font-face pick it up) ->
42
+ * -apple-system/BlinkMacSystemFont (San Francisco on Apple Mail/iOS) ->
43
+ * Segoe UI (effective Outlook Desktop/Windows rendering regardless of CSS)
44
+ * -> Roboto (Gmail's effective default) -> Helvetica/Arial/sans-serif
45
+ * (universal safety net -- guarantees no serif fallback anywhere, since
46
+ * Outlook Desktop falls back all the way to Times New Roman if the whole
47
+ * stack were ever ignored).
48
+ */
49
+ export const fontStack = {
50
+ heading: `'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif`,
51
+ body: `'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif`,
52
+ mono: `ui-monospace, SFMono-Regular, Menlo, Consolas, monospace`,
53
+ };
54
+ export const spacing = {
55
+ xs: 8,
56
+ sm: 16,
57
+ md: 24,
58
+ lg: 32,
59
+ xl: 48,
60
+ };
61
+ /** Matches react-email's own `Container` default (`maxWidth: '37.5em'` ==
62
+ * 600px at a 16px base) -- used by MilaEmailLayout's hybrid Outlook table. */
63
+ export const containerWidth = 600;
64
+ //# sourceMappingURL=tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,MAAM,EAAE,SAAS,EAAE,WAAW;IAC9B,UAAU,EAAE,SAAS,EAAE,WAAW;IAClC,IAAI,EAAE,SAAS;IACf,SAAS,EAAE,SAAS;IACpB,QAAQ,EAAE,SAAS;IACnB,aAAa,EAAE,SAAS;IACxB,UAAU,EAAE,SAAS;IACrB,cAAc,EAAE,SAAS;IACzB,OAAO,EAAE,SAAS,EAAE,UAAU;IAC9B,WAAW,EAAE,SAAS;IACtB,MAAM,EAAE,SAAS,EAAE,WAAW;IAC9B,UAAU,EAAE,SAAS;IACrB,UAAU,EAAE,SAAS;CACb,CAAC;AAEX;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,OAAO,EAAE,8FAA8F;IACvG,IAAI,EAAE,8FAA8F;IACpG,IAAI,EAAE,0DAA0D;CACxD,CAAC;AAEX,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,EAAE,EAAE,CAAC;IACL,EAAE,EAAE,EAAE;IACN,EAAE,EAAE,EAAE;IACN,EAAE,EAAE,EAAE;IACN,EAAE,EAAE,EAAE;CACE,CAAC;AAEX;8EAC8E;AAC9E,MAAM,CAAC,MAAM,cAAc,GAAG,GAAG,CAAC"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@vennyx/mila-react-template",
3
+ "version": "0.1.0",
4
+ "description": "Official React email component library for mila -- bulletproof, bilingual, brand-aware transactional email templates, built on react-email.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "scripts": {
21
+ "build": "tsc -b",
22
+ "test": "vitest run",
23
+ "generate:logo": "node scripts/generate-email-logo.mjs"
24
+ },
25
+ "dependencies": {
26
+ "react-email": "6.7.0"
27
+ },
28
+ "peerDependencies": {
29
+ "react": "^18.0.0 || ^19.0.0",
30
+ "react-dom": "^18.0.0 || ^19.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "26.1.0",
34
+ "@types/react": "19.2.17",
35
+ "@types/react-dom": "19.2.3",
36
+ "react": "19.2.7",
37
+ "react-dom": "19.2.7",
38
+ "sharp": "0.34.5",
39
+ "typescript": "6.0.3",
40
+ "vitest": "4.1.9"
41
+ }
42
+ }