1import { Hct, argbFromHex, hexFromArgb } from "@material/material-color-utilities";
2
3import type { MobileThemeAppearance, MobileThemeVariables } from "./mobileTheme";
4import type { MaterialYouPalette } from "./materialYouPalette";
5
6function withAlpha(color: string, alpha: number): string {
7 const match = /^#([\da-f]{6})(?:[\da-f]{2})?$/iu.exec(color);
8 if (!match?.[1]) return color;
9 const alphaByte = Math.round(Math.min(1, Math.max(0, alpha)) * 255)
10 .toString(16)
11 .padStart(2, "0")
12 .toUpperCase();
13 return `#${match[1]}${alphaByte}`;
14}
15
16function materialTone(color: string, tone: number): string {
17 const match = /^#([\da-f]{6})(?:[\da-f]{2})?$/iu.exec(color);
18 if (!match?.[1]) return color;
19
20 const source = Hct.fromInt(argbFromHex(match[1]));
21 return `${hexFromArgb(Hct.from(source.hue, source.chroma, tone).toInt()).toUpperCase()}FF`;
22}
23
24export function materialYouPaletteToMobileThemeVariables(
25 palette: MaterialYouPalette,
26 appearance: MobileThemeAppearance,
27 base: MobileThemeVariables,
28): MobileThemeVariables {
29 const dark = appearance === "dark";
30 const userBubble = dark ? materialTone(palette.inversePrimary, 30) : palette.primary;
31 const userBubbleForeground = dark ? materialTone(palette.inversePrimary, 90) : palette.onPrimary;
32
33 return {
34 ...base,
35 "--color-screen": palette.surface,
36 "--color-sheet": withAlpha(palette.surfaceContainerLow, 0.98),
37 "--color-sheet-solid": palette.surfaceContainerLow,
38 "--color-card": palette.surfaceContainer,
39 "--color-grouped-card": palette.surfaceContainer,
40 "--color-card-alt": palette.surfaceContainerHigh,
41 "--color-card-translucent": withAlpha(palette.surfaceContainer, 0.8),
42 "--color-thread-canvas": palette.surfaceContainerLow,
43 "--color-thread-selected": palette.surfaceContainer,
44 "--color-thread-selected-foreground": palette.onSurface,
45 "--color-thread-selected-foreground-muted": palette.onSurfaceVariant,
46 "--color-thread-hover": palette.surfaceContainerHigh,
47 "--color-row-hover": palette.surfaceContainerHigh,
48 "--color-composer-panel": withAlpha(palette.surfaceContainerLow, dark ? 0.92 : 0.88),
49 "--color-composer-surface": withAlpha(palette.surfaceContainerHigh, dark ? 0.9 : 0.94),
50 "--color-composer-border": withAlpha(palette.outlineVariant, dark ? 0.34 : 0.46),
51 "--color-foreground": palette.onSurface,
52 "--color-foreground-secondary": palette.onSurfaceVariant,
53 "--color-foreground-muted": palette.onSurfaceVariant,
54 "--color-foreground-tertiary": withAlpha(palette.onSurfaceVariant, 0.58),
55 "--color-border": withAlpha(palette.outlineVariant, dark ? 0.5 : 0.65),
56 "--color-focus": palette.primary,
57 "--color-border-subtle": withAlpha(palette.outlineVariant, dark ? 0.34 : 0.46),
58 "--color-separator": withAlpha(palette.outlineVariant, dark ? 0.24 : 0.34),
59 "--color-subtle": withAlpha(palette.onSurface, 0.05),
60 "--color-subtle-strong": withAlpha(palette.onSurface, 0.1),
61 "--color-inline-skill-background": withAlpha(palette.tertiaryContainer, 0.52),
62 "--color-inline-skill-border": withAlpha(palette.tertiary, 0.38),
63 "--color-inline-skill-foreground": palette.onTertiaryContainer,
64 "--color-primary": palette.primary,
65 "--color-primary-foreground": palette.onPrimary,
66 "--color-primary-text": palette.primary,
67 "--color-primary-shadow": withAlpha(palette.scrim, dark ? 0.22 : 0.18),
68 "--color-secondary": palette.secondaryContainer,
69 "--color-secondary-foreground": palette.onSecondaryContainer,
70 "--color-secondary-border": withAlpha(palette.outlineVariant, dark ? 0.42 : 0.56),
71 "--color-switch-active-track": palette.primary,
72 "--color-switch-active-thumb": palette.onPrimary,
73 "--color-switch-inactive-track": palette.surfaceContainerHighest,
74 "--color-switch-inactive-thumb": palette.outline,
75 "--color-danger": palette.errorContainer,
76 "--color-danger-border": withAlpha(palette.onErrorContainer, dark ? 0.42 : 0.5),
77 "--color-danger-foreground": palette.onErrorContainer,
78 "--color-update": palette.primaryContainer,
79 "--color-update-foreground": palette.onPrimaryContainer,
80 "--color-input": palette.surfaceContainerLowest,
81 "--color-input-border": withAlpha(palette.outline, dark ? 0.42 : 0.5),
82 "--color-sidebar-search": palette.surfaceContainerLowest,
83 "--color-placeholder": withAlpha(palette.onSurface, 0.62),
84 "--color-icon": palette.onSurface,
85 "--color-icon-muted": palette.onSurfaceVariant,
86 "--color-icon-subtle": withAlpha(palette.onSurfaceVariant, 0.68),
87 "--color-header": palette.surfaceContainerHigh,
88 "--color-header-foreground": palette.onSurface,
89 "--color-header-border": withAlpha(palette.outlineVariant, dark ? 0.36 : 0.48),
90 "--color-glass-surface": withAlpha(palette.surfaceContainerHigh, dark ? 0.78 : 0.72),
91 "--color-glass-fallback": withAlpha(palette.surfaceContainerHigh, dark ? 0.9 : 0.94),
92 "--color-glass-tint": withAlpha(palette.primaryContainer, dark ? 0.24 : 0.18),
93 "--color-status-bar": palette.surfaceContainerLow,
94 "--color-md-body": materialTone(palette.onSurfaceVariant, dark ? 90 : 10),
95 "--color-md-strong": materialTone(palette.onSurfaceVariant, dark ? 90 : 10),
96 "--color-md-link": palette.primary,
97 "--color-md-blockquote-border": withAlpha(palette.primary, dark ? 0.34 : 0.26),
98 "--color-md-blockquote-bg": withAlpha(palette.primaryContainer, dark ? 0.14 : 0.2),
99 "--color-md-code-bg": withAlpha(palette.onSurface, dark ? 0.08 : 0.06),
100 "--color-md-code-text": palette.onSurface,
101 "--color-md-user-code-bg": withAlpha(userBubbleForeground, dark ? 0.18 : 0.22),
102 "--color-md-user-code-text": userBubbleForeground,
103 "--color-md-user-fence-bg": withAlpha(userBubbleForeground, dark ? 0.14 : 0.18),
104 "--color-md-user-fence-text": userBubbleForeground,
105 "--color-md-hr": withAlpha(palette.outlineVariant, dark ? 0.48 : 0.6),
106 "--color-user-bubble": userBubble,
107 "--color-user-bubble-foreground": userBubbleForeground,
108 "--color-user-bubble-foreground-muted": withAlpha(userBubbleForeground, 0.78),
109 "--color-user-bubble-skill-foreground": userBubbleForeground,
110 "--color-backdrop": withAlpha(palette.scrim, dark ? 0.48 : 0.22),
111 "--color-drawer": palette.surface,
112 "--color-drawer-foreground": palette.onSurface,
113 "--color-drawer-foreground-muted": palette.onSurfaceVariant,
114 "--color-drawer-border": withAlpha(palette.outlineVariant, dark ? 0.36 : 0.48),
115 "--color-drawer-shadow": withAlpha(palette.scrim, dark ? 0.32 : 0.12),
116 "--color-dot-separator": withAlpha(palette.onSurface, 0.2),
117 "--color-wordmark": palette.onSurface,
118 "--color-chevron": withAlpha(palette.onSurface, 0.2),
119 };
120}