authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-18 21:12:32-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-18 21:12:32-07:00
log0f1b572b9865256dd61e245b4dd09f6a7b15f0e1
treea4eef2cee02beff66f05aadaa3eb90fd9037a9f3
parent5378f87f99175bded2b9241876319567875aca03
signature Signed by PGP key B5690EEEBB952194

fix(mobile): align built-in theme colors with desktop (#12534)


84 files changed, 1913 insertions(+), 1281 deletions(-)

apps/mobile/clerk-theme.json+27-27
......@@ -1,36 +1,36 @@
11{
22 "colors": {
3 "primary": "#262626",
4 "background": "#F2F2F7",
5 "input": "#FFFFFF",
6 "danger": "#DC2626",
3 "primary": "#1b4ed8",
4 "background": "#fcfcfc",
5 "input": "#ffffff",
6 "danger": "#c10007",
77 "success": "#059669",
8 "warning": "#D97706",
9 "foreground": "#262626",
10 "mutedForeground": "#737373",
11 "primaryForeground": "#FFFFFF",
12 "inputForeground": "#262626",
13 "neutral": "#F5F5F5",
14 "border": "#E5E5EA",
15 "ring": "#A3A3A3",
16 "muted": "#F2F2F7",
8 "warning": "#bb4d00",
9 "foreground": "#27272a",
10 "mutedForeground": "#71717b",
11 "primaryForeground": "#ffffff",
12 "inputForeground": "#27272a",
13 "neutral": "#fafafa",
14 "border": "#e4e4e7",
15 "ring": "#1b4ed8",
16 "muted": "#fafafa",
1717 "shadow": "#000000"
1818 },
1919 "darkColors": {
20 "primary": "#F5F5F5",
21 "background": "#0E0E0E",
22 "input": "#171717",
23 "danger": "#FCA5A5",
24 "success": "#34D399",
25 "warning": "#FBBF24",
26 "foreground": "#F5F5F5",
27 "mutedForeground": "#A3A3A3",
28 "primaryForeground": "#0A0A0A",
29 "inputForeground": "#F5F5F5",
30 "neutral": "#1C1C1C",
31 "border": "#2A2A2A",
32 "ring": "#525252",
33 "muted": "#0E0E0E",
20 "primary": "#346bf1",
21 "background": "#0a0a0a",
22 "input": "#111111",
23 "danger": "#ff6467",
24 "success": "#34d399",
25 "warning": "#ffb900",
26 "foreground": "#f5f5f5",
27 "mutedForeground": "#818181",
28 "primaryForeground": "#ffffff",
29 "inputForeground": "#f5f5f5",
30 "neutral": "#111111",
31 "border": "#191919",
32 "ring": "#346bf1",
33 "muted": "#111111",
3434 "shadow": "#000000"
3535 },
3636 "design": {
apps/mobile/generated-uniwind-default-theme-variables.json+150-126
......@@ -1,156 +1,180 @@
11{
22 "light": {
3 "--color-screen": "#f2f2f7",
4 "--color-sheet": "rgba(242, 242, 247, 0.98)",
5 "--color-sheet-solid": "#f2f2f7",
3 "--color-screen": "#fcfcfc",
4 "--color-sheet": "rgba(252, 252, 252, 0.98)",
5 "--color-sheet-solid": "#fcfcfc",
66 "--color-card": "#ffffff",
7 "--color-card-alt": "#f5f5f5",
7 "--color-grouped-card": "#f4f4f5",
8 "--color-card-alt": "#fcfcfc",
89 "--color-card-translucent": "rgba(255, 255, 255, 0.8)",
9 "--color-thread-canvas": "#f5f5f5",
10 "--color-thread-canvas": "#fcfcfc",
1011 "--color-thread-selected": "#ffffff",
11 "--color-thread-selected-foreground": "#262626",
12 "--color-thread-selected-foreground-muted": "#737373",
13 "--color-composer-panel": "rgba(245, 245, 245, 0.88)",
14 "--color-composer-surface": "rgba(255, 255, 255, 0.94)",
15 "--color-composer-border": "rgba(0, 0, 0, 0.1)",
16 "--color-foreground": "#262626",
17 "--color-foreground-secondary": "#525252",
18 "--color-foreground-muted": "#737373",
19 "--color-foreground-tertiary": "#8e8e93",
20 "--color-border": "rgba(0, 0, 0, 0.08)",
21 "--color-border-subtle": "rgba(0, 0, 0, 0.06)",
22 "--color-separator": "rgba(0, 0, 0, 0.04)",
23 "--color-subtle": "rgba(0, 0, 0, 0.04)",
24 "--color-subtle-strong": "rgba(0, 0, 0, 0.08)",
25 "--color-inline-skill-background": "rgba(217, 70, 239, 0.12)",
26 "--color-inline-skill-border": "rgba(217, 70, 239, 0.25)",
27 "--color-inline-skill-foreground": "#a21caf",
28 "--color-primary": "#262626",
12 "--color-thread-selected-foreground": "#27272a",
13 "--color-thread-selected-foreground-muted": "#71717b",
14 "--color-thread-hover": "#fcfcfc",
15 "--color-row-hover": "#f4f4f5",
16 "--color-composer-panel": "rgba(252, 252, 252, 0.88)",
17 "--color-composer-surface": "rgba(244, 244, 245, 0.94)",
18 "--color-composer-border": "rgba(228, 228, 231, 0.8)",
19 "--color-foreground": "#27272a",
20 "--color-foreground-secondary": "#6f6f79",
21 "--color-foreground-muted": "#6f6f79",
22 "--color-foreground-tertiary": "#71717b",
23 "--color-border": "#e4e4e7",
24 "--color-focus": "#1b4ed8",
25 "--color-border-subtle": "rgba(228, 228, 231, 0.7)",
26 "--color-separator": "rgba(228, 228, 231, 0.55)",
27 "--color-subtle": "#fafafa",
28 "--color-subtle-strong": "#fafafa",
29 "--color-inline-skill-background": "#f4f4f5",
30 "--color-inline-skill-border": "rgba(27, 78, 216, 0.42)",
31 "--color-inline-skill-foreground": "#18181b",
32 "--color-primary": "#1b4ed8",
2933 "--color-primary-foreground": "#ffffff",
34 "--color-primary-text": "#1b4ed8",
3035 "--color-primary-shadow": "#000000",
31 "--color-secondary": "#ffffff",
32 "--color-secondary-foreground": "#262626",
33 "--color-secondary-border": "rgba(0, 0, 0, 0.08)",
34 "--color-switch-active-track": "#34c759",
36 "--color-secondary": "#fafafa",
37 "--color-secondary-foreground": "#27272a",
38 "--color-secondary-border": "#e4e4e7",
39 "--color-switch-active-track": "#1b4ed8",
3540 "--color-switch-active-thumb": "#ffffff",
36 "--color-switch-inactive-track": "rgba(0, 0, 0, 0.08)",
37 "--color-switch-inactive-thumb": "#8e8e93",
38 "--color-warning": "#fffbeb",
39 "--color-warning-border": "#fde68a",
40 "--color-warning-foreground": "#b45309",
41 "--color-danger": "#fef2f2",
42 "--color-danger-border": "rgba(239, 68, 68, 0.12)",
43 "--color-danger-foreground": "#dc2626",
41 "--color-switch-inactive-track": "#fafafa",
42 "--color-switch-inactive-thumb": "#71717b",
43 "--color-warning": "#fcf4e8",
44 "--color-warning-border": "rgba(254, 154, 0, 0.32)",
45 "--color-warning-foreground": "#bb4d00",
46 "--color-danger": "#fcebec",
47 "--color-danger-border": "rgba(251, 44, 54, 0.32)",
48 "--color-danger-foreground": "#c10007",
49 "--color-update": "#e0e6f7",
50 "--color-update-foreground": "#1b4ed8",
4451 "--color-input": "#ffffff",
45 "--color-input-border": "rgba(0, 0, 0, 0.1)",
46 "--color-sidebar-search": "rgba(118, 118, 128, 0.12)",
47 "--color-placeholder": "#737373",
48 "--color-icon": "#262626",
49 "--color-icon-muted": "#525252",
50 "--color-icon-subtle": "#a3a3a3",
51 "--color-header": "rgba(255, 255, 255, 0.97)",
52 "--color-header-border": "rgba(0, 0, 0, 0.06)",
53 "--color-glass-surface": "rgba(255, 255, 255, 0.72)",
54 "--color-glass-tint": "rgba(255, 255, 255, 0.18)",
55 "--color-status-bar": "#f2f2f7",
56 "--color-md-body": "#111111",
57 "--color-md-strong": "#000000",
58 "--color-md-link": "#2563eb",
59 "--color-md-blockquote-border": "rgba(0, 0, 0, 0.08)",
60 "--color-md-blockquote-bg": "rgba(0, 0, 0, 0.02)",
61 "--color-md-code-bg": "rgba(0, 0, 0, 0.04)",
62 "--color-md-code-text": "#262626",
63 "--color-md-user-code-bg": "rgba(0, 0, 0, 0.04)",
64 "--color-md-user-code-text": "#262626",
65 "--color-md-user-fence-bg": "rgba(0, 0, 0, 0.06)",
66 "--color-md-user-fence-text": "#262626",
67 "--color-md-hr": "rgba(0, 0, 0, 0.08)",
68 "--color-user-bubble": "#ffffff",
69 "--color-user-bubble-foreground": "#262626",
70 "--color-user-bubble-foreground-muted": "rgba(38, 38, 38, 0.78)",
71 "--color-user-bubble-skill-foreground": "#2563eb",
52 "--color-input-border": "#d4d4d8",
53 "--color-sidebar-search": "#f4f4f5",
54 "--color-placeholder": "#6f6f79",
55 "--color-icon": "#27272a",
56 "--color-icon-muted": "#71717b",
57 "--color-icon-subtle": "#71717b",
58 "--color-header": "rgba(252, 252, 252, 0.97)",
59 "--color-header-foreground": "#27272a",
60 "--color-header-border": "#e4e4e7",
61 "--color-glass-surface": "rgba(255, 255, 255, 0.74)",
62 "--color-glass-fallback": "rgba(244, 244, 245, 0.94)",
63 "--color-glass-tint": "rgba(255, 255, 255, 0.22)",
64 "--color-status-bar": "#fcfcfc",
65 "--color-md-body": "#27272a",
66 "--color-md-strong": "#27272a",
67 "--color-md-link": "#1b4ed8",
68 "--color-md-blockquote-border": "#e4e4e7",
69 "--color-md-blockquote-bg": "#fafafa",
70 "--color-md-code-bg": "#ffffff",
71 "--color-md-code-text": "#27272a",
72 "--color-md-user-code-bg": "rgba(39, 39, 42, 0.18)",
73 "--color-md-user-code-text": "#27272a",
74 "--color-md-user-fence-bg": "rgba(0, 0, 0, 0.16)",
75 "--color-md-user-fence-text": "#27272a",
76 "--color-md-hr": "#e4e4e7",
77 "--color-user-bubble": "#efeff1",
78 "--color-user-bubble-foreground": "#27272a",
79 "--color-user-bubble-foreground-muted": "rgba(39, 39, 42, 0.78)",
80 "--color-user-bubble-skill-foreground": "#1b4ed8",
7281 "--color-backdrop": "rgba(0, 0, 0, 0.22)",
73 "--color-drawer": "rgba(255, 255, 255, 0.99)",
82 "--color-drawer": "#fafafa",
83 "--color-drawer-foreground": "#27272a",
84 "--color-drawer-foreground-muted": "#71717b",
85 "--color-drawer-border": "#e4e4e7",
7486 "--color-drawer-shadow": "rgba(0, 0, 0, 0.12)",
75 "--color-dot-separator": "rgba(0, 0, 0, 0.2)",
76 "--color-wordmark": "#262626",
77 "--color-chevron": "rgba(0, 0, 0, 0.2)"
87 "--color-dot-separator": "rgba(113, 113, 123, 0.35)",
88 "--color-wordmark": "#27272a",
89 "--color-chevron": "rgba(113, 113, 123, 0.42)"
7890 },
7991 "dark": {
8092 "--color-screen": "#0a0a0a",
81 "--color-sheet": "rgba(14, 14, 14, 0.98)",
82 "--color-sheet-solid": "#0e0e0e",
83 "--color-card": "#171717",
84 "--color-card-alt": "#1c1c1c",
93 "--color-sheet": "rgba(10, 10, 10, 0.98)",
94 "--color-sheet-solid": "#0a0a0a",
95 "--color-card": "#111111",
96 "--color-grouped-card": "#1a1b1b",
97 "--color-card-alt": "#111111",
8598 "--color-card-translucent": "rgba(17, 17, 17, 0.8)",
86 "--color-thread-canvas": "#1c1c1c",
87 "--color-thread-selected": "#171717",
88 "--color-thread-selected-foreground": "#f5f5f5",
89 "--color-thread-selected-foreground-muted": "#8e8e93",
90 "--color-composer-panel": "rgba(28, 28, 28, 0.92)",
91 "--color-composer-surface": "rgba(23, 23, 23, 0.9)",
92 "--color-composer-border": "rgba(255, 255, 255, 0.08)",
99 "--color-thread-canvas": "#0a0a0a",
100 "--color-thread-selected": "#1a1b1b",
101 "--color-thread-selected-foreground": "#f1f3f7",
102 "--color-thread-selected-foreground-muted": "#a3a3a3",
103 "--color-thread-hover": "#131313",
104 "--color-row-hover": "#141414",
105 "--color-composer-panel": "rgba(10, 10, 10, 0.92)",
106 "--color-composer-surface": "rgba(26, 27, 27, 0.9)",
107 "--color-composer-border": "rgba(25, 25, 25, 0.8)",
93108 "--color-foreground": "#f5f5f5",
94 "--color-foreground-secondary": "#a3a3a3",
95 "--color-foreground-muted": "#8e8e93",
96 "--color-foreground-tertiary": "#636366",
97 "--color-border": "rgba(255, 255, 255, 0.06)",
98 "--color-border-subtle": "rgba(255, 255, 255, 0.04)",
99 "--color-separator": "rgba(255, 255, 255, 0.03)",
100 "--color-subtle": "rgba(255, 255, 255, 0.04)",
101 "--color-subtle-strong": "rgba(255, 255, 255, 0.08)",
102 "--color-inline-skill-background": "rgba(217, 70, 239, 0.12)",
103 "--color-inline-skill-border": "rgba(217, 70, 239, 0.25)",
104 "--color-inline-skill-foreground": "#f0abfc",
105 "--color-primary": "#f5f5f5",
106 "--color-primary-foreground": "#0a0a0a",
109 "--color-foreground-secondary": "#838383",
110 "--color-foreground-muted": "#838383",
111 "--color-foreground-tertiary": "#818181",
112 "--color-border": "#191919",
113 "--color-focus": "#346bf1",
114 "--color-border-subtle": "rgba(25, 25, 25, 0.7)",
115 "--color-separator": "rgba(25, 25, 25, 0.55)",
116 "--color-subtle": "#111111",
117 "--color-subtle-strong": "#111111",
118 "--color-inline-skill-background": "#141414",
119 "--color-inline-skill-border": "rgba(52, 107, 241, 0.42)",
120 "--color-inline-skill-foreground": "#f5f5f5",
121 "--color-primary": "#346bf1",
122 "--color-primary-foreground": "#ffffff",
123 "--color-primary-text": "#4b7cf3",
107124 "--color-primary-shadow": "#000000",
108 "--color-secondary": "rgba(255, 255, 255, 0.04)",
125 "--color-secondary": "#111111",
109126 "--color-secondary-foreground": "#f5f5f5",
110 "--color-secondary-border": "rgba(255, 255, 255, 0.06)",
111 "--color-switch-active-track": "#30d158",
127 "--color-secondary-border": "#191919",
128 "--color-switch-active-track": "#346bf1",
112129 "--color-switch-active-thumb": "#ffffff",
113 "--color-switch-inactive-track": "rgba(255, 255, 255, 0.06)",
114 "--color-switch-inactive-thumb": "#8e8e93",
115 "--color-warning": "rgba(69, 26, 3, 0.4)",
116 "--color-warning-border": "rgba(120, 53, 15, 0.6)",
117 "--color-warning-foreground": "#fcd34d",
118 "--color-danger": "rgba(239, 68, 68, 0.14)",
119 "--color-danger-border": "rgba(248, 113, 113, 0.18)",
120 "--color-danger-foreground": "#fca5a5",
121 "--color-input": "#141414",
122 "--color-input-border": "rgba(255, 255, 255, 0.08)",
123 "--color-sidebar-search": "rgba(118, 118, 128, 0.24)",
124 "--color-placeholder": "#8e8e93",
130 "--color-switch-inactive-track": "#111111",
131 "--color-switch-inactive-thumb": "#818181",
132 "--color-warning": "#312108",
133 "--color-warning-border": "rgba(254, 154, 0, 0.32)",
134 "--color-warning-foreground": "#ffb900",
135 "--color-danger": "#301214",
136 "--color-danger-border": "rgba(251, 65, 74, 0.32)",
137 "--color-danger-foreground": "#ff6467",
138 "--color-update": "#121b34",
139 "--color-update-foreground": "#51a2ff",
140 "--color-input": "#111111",
141 "--color-input-border": "#1e1e1e",
142 "--color-sidebar-search": "#0a0a0a",
143 "--color-placeholder": "#838383",
125144 "--color-icon": "#f5f5f5",
126 "--color-icon-muted": "#a3a3a3",
127 "--color-icon-subtle": "#8e8e93",
145 "--color-icon-muted": "#818181",
146 "--color-icon-subtle": "#818181",
128147 "--color-header": "rgba(10, 10, 10, 0.97)",
129 "--color-header-border": "rgba(255, 255, 255, 0.06)",
130 "--color-glass-surface": "rgba(23, 23, 23, 0.78)",
131 "--color-glass-tint": "rgba(23, 23, 23, 0.24)",
148 "--color-header-foreground": "#f5f5f5",
149 "--color-header-border": "#191919",
150 "--color-glass-surface": "rgba(17, 17, 17, 0.74)",
151 "--color-glass-fallback": "rgba(26, 27, 27, 0.9)",
152 "--color-glass-tint": "rgba(17, 17, 17, 0.22)",
132153 "--color-status-bar": "#0a0a0a",
133 "--color-md-body": "#e5e5e5",
154 "--color-md-body": "#f5f5f5",
134155 "--color-md-strong": "#f5f5f5",
135 "--color-md-link": "#60a5fa",
136 "--color-md-blockquote-border": "rgba(255, 255, 255, 0.1)",
137 "--color-md-blockquote-bg": "rgba(255, 255, 255, 0.03)",
138 "--color-md-code-bg": "rgba(255, 255, 255, 0.06)",
139 "--color-md-code-text": "#e5e5e5",
140 "--color-md-user-code-bg": "rgba(255, 255, 255, 0.06)",
141 "--color-md-user-code-text": "#e5e5e5",
142 "--color-md-user-fence-bg": "rgba(255, 255, 255, 0.09)",
143 "--color-md-user-fence-text": "#e5e5e5",
144 "--color-md-hr": "rgba(255, 255, 255, 0.08)",
145 "--color-user-bubble": "#171717",
156 "--color-md-link": "#3b70f1",
157 "--color-md-blockquote-border": "#191919",
158 "--color-md-blockquote-bg": "#111111",
159 "--color-md-code-bg": "#111111",
160 "--color-md-code-text": "#f5f5f5",
161 "--color-md-user-code-bg": "rgba(245, 245, 245, 0.18)",
162 "--color-md-user-code-text": "#f5f5f5",
163 "--color-md-user-fence-bg": "rgba(0, 0, 0, 0.28)",
164 "--color-md-user-fence-text": "#f5f5f5",
165 "--color-md-hr": "#191919",
166 "--color-user-bubble": "#161616",
146167 "--color-user-bubble-foreground": "#f5f5f5",
147168 "--color-user-bubble-foreground-muted": "rgba(245, 245, 245, 0.78)",
148 "--color-user-bubble-skill-foreground": "#60a5fa",
169 "--color-user-bubble-skill-foreground": "#4678f2",
149170 "--color-backdrop": "rgba(0, 0, 0, 0.48)",
150 "--color-drawer": "rgba(14, 14, 14, 0.99)",
171 "--color-drawer": "#000000",
172 "--color-drawer-foreground": "#f1f3f7",
173 "--color-drawer-foreground-muted": "#a3a3a3",
174 "--color-drawer-border": "#141414",
151175 "--color-drawer-shadow": "rgba(0, 0, 0, 0.32)",
152 "--color-dot-separator": "rgba(255, 255, 255, 0.2)",
176 "--color-dot-separator": "rgba(129, 129, 129, 0.35)",
153177 "--color-wordmark": "#f5f5f5",
154 "--color-chevron": "rgba(255, 255, 255, 0.2)"
178 "--color-chevron": "rgba(129, 129, 129, 0.42)"
155179 }
156180}
apps/mobile/generated-uniwind-themes.css+470-176
......@@ -2,6 +2,93 @@
22@layer theme {
33 :root {
44 @variant light {
5 --color-screen: #fcfcfc;
6 --color-sheet: rgba(252, 252, 252, 0.98);
7 --color-sheet-solid: #fcfcfc;
8 --color-card: #ffffff;
9 --color-grouped-card: #f4f4f5;
10 --color-card-alt: #fcfcfc;
11 --color-card-translucent: rgba(255, 255, 255, 0.8);
12 --color-thread-canvas: #fcfcfc;
13 --color-thread-selected: #ffffff;
14 --color-thread-selected-foreground: #27272a;
15 --color-thread-selected-foreground-muted: #71717b;
16 --color-thread-hover: #fcfcfc;
17 --color-row-hover: #f4f4f5;
18 --color-composer-panel: rgba(252, 252, 252, 0.88);
19 --color-composer-surface: rgba(244, 244, 245, 0.94);
20 --color-composer-border: rgba(228, 228, 231, 0.8);
21 --color-foreground: #27272a;
22 --color-foreground-secondary: #6f6f79;
23 --color-foreground-muted: #6f6f79;
24 --color-foreground-tertiary: #71717b;
25 --color-border: #e4e4e7;
26 --color-focus: #1b4ed8;
27 --color-border-subtle: rgba(228, 228, 231, 0.7);
28 --color-separator: rgba(228, 228, 231, 0.55);
29 --color-subtle: #fafafa;
30 --color-subtle-strong: #fafafa;
31 --color-inline-skill-background: #f4f4f5;
32 --color-inline-skill-border: rgba(27, 78, 216, 0.42);
33 --color-inline-skill-foreground: #18181b;
34 --color-primary: #1b4ed8;
35 --color-primary-foreground: #ffffff;
36 --color-primary-text: #1b4ed8;
37 --color-primary-shadow: #000000;
38 --color-secondary: #fafafa;
39 --color-secondary-foreground: #27272a;
40 --color-secondary-border: #e4e4e7;
41 --color-switch-active-track: #1b4ed8;
42 --color-switch-active-thumb: #ffffff;
43 --color-switch-inactive-track: #fafafa;
44 --color-switch-inactive-thumb: #71717b;
45 --color-warning: #fcf4e8;
46 --color-warning-border: rgba(254, 154, 0, 0.32);
47 --color-warning-foreground: #bb4d00;
48 --color-danger: #fcebec;
49 --color-danger-border: rgba(251, 44, 54, 0.32);
50 --color-danger-foreground: #c10007;
51 --color-update: #e0e6f7;
52 --color-update-foreground: #1b4ed8;
53 --color-input: #ffffff;
54 --color-input-border: #d4d4d8;
55 --color-sidebar-search: #f4f4f5;
56 --color-placeholder: #6f6f79;
57 --color-icon: #27272a;
58 --color-icon-muted: #71717b;
59 --color-icon-subtle: #71717b;
60 --color-header: rgba(252, 252, 252, 0.97);
61 --color-header-foreground: #27272a;
62 --color-header-border: #e4e4e7;
63 --color-glass-surface: rgba(255, 255, 255, 0.74);
64 --color-glass-fallback: rgba(244, 244, 245, 0.94);
65 --color-glass-tint: rgba(255, 255, 255, 0.22);
66 --color-status-bar: #fcfcfc;
67 --color-md-body: #27272a;
68 --color-md-strong: #27272a;
69 --color-md-link: #1b4ed8;
70 --color-md-blockquote-border: #e4e4e7;
71 --color-md-blockquote-bg: #fafafa;
72 --color-md-code-bg: #ffffff;
73 --color-md-code-text: #27272a;
74 --color-md-user-code-bg: rgba(39, 39, 42, 0.18);
75 --color-md-user-code-text: #27272a;
76 --color-md-user-fence-bg: rgba(0, 0, 0, 0.16);
77 --color-md-user-fence-text: #27272a;
78 --color-md-hr: #e4e4e7;
79 --color-user-bubble: #efeff1;
80 --color-user-bubble-foreground: #27272a;
81 --color-user-bubble-foreground-muted: rgba(39, 39, 42, 0.78);
82 --color-user-bubble-skill-foreground: #1b4ed8;
83 --color-backdrop: rgba(0, 0, 0, 0.22);
84 --color-drawer: #fafafa;
85 --color-drawer-foreground: #27272a;
86 --color-drawer-foreground-muted: #71717b;
87 --color-drawer-border: #e4e4e7;
88 --color-drawer-shadow: rgba(0, 0, 0, 0.12);
89 --color-dot-separator: rgba(113, 113, 123, 0.35);
90 --color-wordmark: #27272a;
91 --color-chevron: rgba(113, 113, 123, 0.42);
592 --color-adaptive-amber-50-950-a40: oklch(98.7% 0.022 95.277);
693 --color-adaptive-amber-200-900-a60: oklch(92.4% 0.12 95.746);
794 --color-adaptive-amber-500-a12-a16: oklch(76.9% 0.188 70.08 / 12%);
......@@ -59,14 +146,101 @@
59146 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);
60147 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);
61148 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);
62 --color-clerk-page: #f2f2f7;
63 --color-clerk-foreground: #262626;
64 --color-clerk-foreground-muted: #737373;
65 --color-clerk-border: rgba(229, 229, 234, 0.06);
66 --color-clerk-danger: #dc2626;
149 --color-clerk-page: #fcfcfc;
150 --color-clerk-foreground: #27272a;
151 --color-clerk-foreground-muted: #71717b;
152 --color-clerk-border: #e4e4e7;
153 --color-clerk-danger: #c10007;
67154 }
68155
69156 @variant dark {
157 --color-screen: #0a0a0a;
158 --color-sheet: rgba(10, 10, 10, 0.98);
159 --color-sheet-solid: #0a0a0a;
160 --color-card: #111111;
161 --color-grouped-card: #1a1b1b;
162 --color-card-alt: #111111;
163 --color-card-translucent: rgba(17, 17, 17, 0.8);
164 --color-thread-canvas: #0a0a0a;
165 --color-thread-selected: #1a1b1b;
166 --color-thread-selected-foreground: #f1f3f7;
167 --color-thread-selected-foreground-muted: #a3a3a3;
168 --color-thread-hover: #131313;
169 --color-row-hover: #141414;
170 --color-composer-panel: rgba(10, 10, 10, 0.92);
171 --color-composer-surface: rgba(26, 27, 27, 0.9);
172 --color-composer-border: rgba(25, 25, 25, 0.8);
173 --color-foreground: #f5f5f5;
174 --color-foreground-secondary: #838383;
175 --color-foreground-muted: #838383;
176 --color-foreground-tertiary: #818181;
177 --color-border: #191919;
178 --color-focus: #346bf1;
179 --color-border-subtle: rgba(25, 25, 25, 0.7);
180 --color-separator: rgba(25, 25, 25, 0.55);
181 --color-subtle: #111111;
182 --color-subtle-strong: #111111;
183 --color-inline-skill-background: #141414;
184 --color-inline-skill-border: rgba(52, 107, 241, 0.42);
185 --color-inline-skill-foreground: #f5f5f5;
186 --color-primary: #346bf1;
187 --color-primary-foreground: #ffffff;
188 --color-primary-text: #4b7cf3;
189 --color-primary-shadow: #000000;
190 --color-secondary: #111111;
191 --color-secondary-foreground: #f5f5f5;
192 --color-secondary-border: #191919;
193 --color-switch-active-track: #346bf1;
194 --color-switch-active-thumb: #ffffff;
195 --color-switch-inactive-track: #111111;
196 --color-switch-inactive-thumb: #818181;
197 --color-warning: #312108;
198 --color-warning-border: rgba(254, 154, 0, 0.32);
199 --color-warning-foreground: #ffb900;
200 --color-danger: #301214;
201 --color-danger-border: rgba(251, 65, 74, 0.32);
202 --color-danger-foreground: #ff6467;
203 --color-update: #121b34;
204 --color-update-foreground: #51a2ff;
205 --color-input: #111111;
206 --color-input-border: #1e1e1e;
207 --color-sidebar-search: #0a0a0a;
208 --color-placeholder: #838383;
209 --color-icon: #f5f5f5;
210 --color-icon-muted: #818181;
211 --color-icon-subtle: #818181;
212 --color-header: rgba(10, 10, 10, 0.97);
213 --color-header-foreground: #f5f5f5;
214 --color-header-border: #191919;
215 --color-glass-surface: rgba(17, 17, 17, 0.74);
216 --color-glass-fallback: rgba(26, 27, 27, 0.9);
217 --color-glass-tint: rgba(17, 17, 17, 0.22);
218 --color-status-bar: #0a0a0a;
219 --color-md-body: #f5f5f5;
220 --color-md-strong: #f5f5f5;
221 --color-md-link: #3b70f1;
222 --color-md-blockquote-border: #191919;
223 --color-md-blockquote-bg: #111111;
224 --color-md-code-bg: #111111;
225 --color-md-code-text: #f5f5f5;
226 --color-md-user-code-bg: rgba(245, 245, 245, 0.18);
227 --color-md-user-code-text: #f5f5f5;
228 --color-md-user-fence-bg: rgba(0, 0, 0, 0.28);
229 --color-md-user-fence-text: #f5f5f5;
230 --color-md-hr: #191919;
231 --color-user-bubble: #161616;
232 --color-user-bubble-foreground: #f5f5f5;
233 --color-user-bubble-foreground-muted: rgba(245, 245, 245, 0.78);
234 --color-user-bubble-skill-foreground: #4678f2;
235 --color-backdrop: rgba(0, 0, 0, 0.48);
236 --color-drawer: #000000;
237 --color-drawer-foreground: #f1f3f7;
238 --color-drawer-foreground-muted: #a3a3a3;
239 --color-drawer-border: #141414;
240 --color-drawer-shadow: rgba(0, 0, 0, 0.32);
241 --color-dot-separator: rgba(129, 129, 129, 0.35);
242 --color-wordmark: #f5f5f5;
243 --color-chevron: rgba(129, 129, 129, 0.42);
70244 --color-adaptive-amber-50-950-a40: oklch(27.9% 0.077 45.635 / 40%);
71245 --color-adaptive-amber-200-900-a60: oklch(41.4% 0.112 45.904 / 60%);
72246 --color-adaptive-amber-500-a12-a16: oklch(76.9% 0.188 70.08 / 16%);
......@@ -124,32 +298,36 @@
124298 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);
125299 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);
126300 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);
127 --color-clerk-page: #0e0e0e;
301 --color-clerk-page: #0a0a0a;
128302 --color-clerk-foreground: #f5f5f5;
129 --color-clerk-foreground-muted: #a3a3a3;
130 --color-clerk-border: rgba(42, 42, 42, 0.06);
131 --color-clerk-danger: #fca5a5;
303 --color-clerk-foreground-muted: #818181;
304 --color-clerk-border: #191919;
305 --color-clerk-danger: #ff6467;
132306 }
133307
134308 @variant t3-chat-light {
135309 --color-screen: #fdf7fd;
136310 --color-sheet: rgba(253, 247, 253, 0.98);
137311 --color-sheet-solid: #fdf7fd;
138 --color-card: #fdfafd;
139 --color-card-alt: #faf3fb;
140 --color-card-translucent: rgba(253, 250, 253, 0.8);
141 --color-thread-canvas: #faf3fb;
142 --color-thread-selected: #fdfafd;
143 --color-thread-selected-foreground: #501854;
312 --color-card: #faf3fb;
313 --color-grouped-card: #faf3fb;
314 --color-card-alt: #fdfafd;
315 --color-card-translucent: rgba(250, 243, 251, 0.8);
316 --color-thread-canvas: #fdf7fd;
317 --color-thread-selected: #f8f8f7;
318 --color-thread-selected-foreground: #454554;
144319 --color-thread-selected-foreground-muted: #ac1668;
145 --color-composer-panel: rgba(250, 243, 251, 0.88);
146 --color-composer-surface: rgba(253, 250, 253, 0.94);
320 --color-thread-hover: #f8f8f7;
321 --color-row-hover: #eccfe3;
322 --color-composer-panel: rgba(253, 247, 253, 0.88);
323 --color-composer-surface: rgba(250, 243, 251, 0.94);
147324 --color-composer-border: rgba(238, 225, 237, 0.54);
148325 --color-foreground: #501854;
149326 --color-foreground-secondary: #ac1668;
150327 --color-foreground-muted: #8d1255;
151328 --color-foreground-tertiary: #ac1668;
152329 --color-border: #eee1ed;
330 --color-focus: #db2777;
153331 --color-border-subtle: rgba(238, 225, 237, 0.7);
154332 --color-separator: rgba(238, 225, 237, 0.55);
155333 --color-subtle: #eaa7cb;
......@@ -159,6 +337,7 @@
159337 --color-inline-skill-foreground: #454554;
160338 --color-primary: #db2777;
161339 --color-primary-foreground: #ffffff;
340 --color-primary-text: #d32572;
162341 --color-primary-shadow: #000000;
163342 --color-secondary: #f1c4e6;
164343 --color-secondary-foreground: #77347c;
......@@ -173,7 +352,9 @@
173352 --color-danger: #fde4f1;
174353 --color-danger-border: rgba(247, 8, 108, 0.32);
175354 --color-danger-foreground: #9d174d;
176 --color-input: #fdfafd;
355 --color-update: #fadfef;
356 --color-update-foreground: #ac1668;
357 --color-input: #faf3fb;
177358 --color-input-border: #e7c1dc;
178359 --color-sidebar-search: #f8f8f7;
179360 --color-placeholder: #8b5f90;
......@@ -181,13 +362,15 @@
181362 --color-icon-muted: #ac1668;
182363 --color-icon-subtle: #ac1668;
183364 --color-header: rgba(253, 247, 253, 0.97);
365 --color-header-foreground: #501854;
184366 --color-header-border: #efbdeb;
185367 --color-glass-surface: rgba(255, 255, 255, 0.74);
368 --color-glass-fallback: rgba(250, 243, 251, 0.94);
186369 --color-glass-tint: rgba(255, 255, 255, 0.22);
187370 --color-status-bar: #fdf7fd;
188371 --color-md-body: #501854;
189372 --color-md-strong: #501854;
190 --color-md-link: #db2777;
373 --color-md-link: #d62675;
191374 --color-md-blockquote-border: #eee1ed;
192375 --color-md-blockquote-bg: #eaa7cb;
193376 --color-md-code-bg: #f5ecf9;
......@@ -202,7 +385,10 @@
202385 --color-user-bubble-foreground-muted: rgba(73, 44, 97, 0.78);
203386 --color-user-bubble-skill-foreground: #c12269;
204387 --color-backdrop: rgba(0, 0, 0, 0.22);
205 --color-drawer: rgba(242, 225, 244, 0.99);
388 --color-drawer: #f2e1f4;
389 --color-drawer-foreground: #454554;
390 --color-drawer-foreground-muted: #ac1668;
391 --color-drawer-border: #eceae9;
206392 --color-drawer-shadow: rgba(0, 0, 0, 0.12);
207393 --color-dot-separator: rgba(172, 22, 104, 0.35);
208394 --color-wordmark: #501854;
......@@ -264,32 +450,36 @@
264450 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);
265451 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);
266452 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);
267 --color-clerk-page: #f2f2f7;
268 --color-clerk-foreground: #262626;
269 --color-clerk-foreground-muted: #737373;
270 --color-clerk-border: rgba(229, 229, 234, 0.06);
271 --color-clerk-danger: #dc2626;
453 --color-clerk-page: #fcfcfc;
454 --color-clerk-foreground: #27272a;
455 --color-clerk-foreground-muted: #71717b;
456 --color-clerk-border: #e4e4e7;
457 --color-clerk-danger: #c10007;
272458 }
273459
274460 @variant t3-chat-dark {
275461 --color-screen: #1f1a24;
276462 --color-sheet: rgba(31, 26, 36, 0.98);
277463 --color-sheet-solid: #1f1a24;
278 --color-card: #2c2631;
279 --color-card-alt: #29232d;
280 --color-card-translucent: rgba(44, 38, 49, 0.8);
281 --color-thread-canvas: #29232d;
282 --color-thread-selected: #2c2631;
283 --color-thread-selected-foreground: #f9f8fb;
464 --color-card: #29232d;
465 --color-grouped-card: #29232d;
466 --color-card-alt: #2c2631;
467 --color-card-translucent: rgba(41, 35, 45, 0.8);
468 --color-thread-canvas: #1f1a24;
469 --color-thread-selected: #261922;
470 --color-thread-selected-foreground: #f4f4f5;
284471 --color-thread-selected-foreground-muted: #e7d0dd;
285 --color-composer-panel: rgba(41, 35, 45, 0.92);
286 --color-composer-surface: rgba(44, 38, 49, 0.9);
472 --color-thread-hover: #261922;
473 --color-row-hover: #463753;
474 --color-composer-panel: rgba(31, 26, 36, 0.92);
475 --color-composer-surface: rgba(41, 35, 45, 0.9);
287476 --color-composer-border: rgba(39, 36, 44, 0.46);
288477 --color-foreground: #f9f8fb;
289478 --color-foreground-secondary: #e7d0dd;
290479 --color-foreground-muted: #e7d0dd;
291480 --color-foreground-tertiary: #e7d0dd;
292481 --color-border: #27242c;
482 --color-focus: #db2777;
293483 --color-border-subtle: rgba(39, 36, 44, 0.7);
294484 --color-separator: rgba(39, 36, 44, 0.55);
295485 --color-subtle: #423a45;
......@@ -299,6 +489,7 @@
299489 --color-inline-skill-foreground: #f8f1f5;
300490 --color-primary: #a3004c;
301491 --color-primary-foreground: #fbd0e8;
492 --color-primary-text: #cc729c;
302493 --color-primary-shadow: #000000;
303494 --color-secondary: #362d3d;
304495 --color-secondary-foreground: #d4c7e1;
......@@ -313,7 +504,9 @@
313504 --color-danger: #331a2b;
314505 --color-danger-border: rgba(157, 23, 77, 0.32);
315506 --color-danger-foreground: #fbd0e8;
316 --color-input: #2c2631;
507 --color-update: #37152b;
508 --color-update-foreground: #fbd0e8;
509 --color-input: #29232d;
317510 --color-input-border: #302029;
318511 --color-sidebar-search: #261922;
319512 --color-placeholder: #968d9f;
......@@ -321,13 +514,15 @@
321514 --color-icon-muted: #d4c7e1;
322515 --color-icon-subtle: #e7d0dd;
323516 --color-header: rgba(31, 26, 36, 0.97);
517 --color-header-foreground: #f9f8fb;
324518 --color-header-border: #27242c;
325519 --color-glass-surface: rgba(16, 10, 14, 0.74);
520 --color-glass-fallback: rgba(41, 35, 45, 0.9);
326521 --color-glass-tint: rgba(16, 10, 14, 0.22);
327522 --color-status-bar: #1f1a24;
328523 --color-md-body: #f9f8fb;
329524 --color-md-strong: #f9f8fb;
330 --color-md-link: #a3004c;
525 --color-md-link: #c66290;
331526 --color-md-blockquote-border: #27242c;
332527 --color-md-blockquote-bg: #423a45;
333528 --color-md-code-bg: #1f1a24;
......@@ -342,7 +537,10 @@
342537 --color-user-bubble-foreground-muted: rgba(242, 235, 250, 0.78);
343538 --color-user-bubble-skill-foreground: #cb709a;
344539 --color-backdrop: rgba(0, 0, 0, 0.48);
345 --color-drawer: rgba(23, 16, 24, 0.99);
540 --color-drawer: #171018;
541 --color-drawer-foreground: #f4f4f5;
542 --color-drawer-foreground-muted: #e7d0dd;
543 --color-drawer-border: #322028;
346544 --color-drawer-shadow: rgba(0, 0, 0, 0.32);
347545 --color-dot-separator: rgba(231, 208, 221, 0.35);
348546 --color-wordmark: #f9f8fb;
......@@ -404,32 +602,36 @@
404602 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);
405603 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);
406604 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);
407 --color-clerk-page: #0e0e0e;
605 --color-clerk-page: #0a0a0a;
408606 --color-clerk-foreground: #f5f5f5;
409 --color-clerk-foreground-muted: #a3a3a3;
410 --color-clerk-border: rgba(42, 42, 42, 0.06);
411 --color-clerk-danger: #fca5a5;
607 --color-clerk-foreground-muted: #818181;
608 --color-clerk-border: #191919;
609 --color-clerk-danger: #ff6467;
412610 }
413611
414612 @variant grove-light {
415613 --color-screen: #f3f7f4;
416614 --color-sheet: rgba(243, 247, 244, 0.98);
417615 --color-sheet-solid: #f3f7f4;
418 --color-card: #ecefed;
419 --color-card-alt: #f3f7f4;
420 --color-card-translucent: rgba(236, 239, 237, 0.8);
616 --color-card: #f3f7f4;
617 --color-grouped-card: #f3f7f4;
618 --color-card-alt: #ecefed;
619 --color-card-translucent: rgba(243, 247, 244, 0.8);
421620 --color-thread-canvas: #f3f7f4;
422 --color-thread-selected: #ecefed;
621 --color-thread-selected: #bad7c9;
423622 --color-thread-selected-foreground: #241523;
424 --color-thread-selected-foreground-muted: #746c73;
623 --color-thread-selected-foreground-muted: #5d585e;
624 --color-thread-hover: #cae0d5;
625 --color-row-hover: #d5e6dd;
425626 --color-composer-panel: rgba(243, 247, 244, 0.88);
426 --color-composer-surface: rgba(236, 239, 237, 0.94);
627 --color-composer-surface: rgba(243, 247, 244, 0.94);
427628 --color-composer-border: rgba(203, 213, 209, 0.54);
428629 --color-foreground: #241523;
429 --color-foreground-secondary: #746c73;
630 --color-foreground-secondary: #726a71;
430631 --color-foreground-muted: #6e696f;
431632 --color-foreground-tertiary: #746c73;
432633 --color-border: #cbd5d1;
634 --color-focus: #1b7d50;
433635 --color-border-subtle: rgba(203, 213, 209, 0.7);
434636 --color-separator: rgba(203, 213, 209, 0.55);
435637 --color-subtle: #e6f0ea;
......@@ -437,13 +639,14 @@
437639 --color-inline-skill-background: #d5e6dd;
438640 --color-inline-skill-border: rgba(27, 125, 80, 0.42);
439641 --color-inline-skill-foreground: #241523;
440 --color-primary: #1b7d50;
642 --color-primary: #8f6410;
441643 --color-primary-foreground: #fffaff;
644 --color-primary-text: #8f6410;
442645 --color-primary-shadow: #000000;
443646 --color-secondary: #e2ede7;
444647 --color-secondary-foreground: #241523;
445648 --color-secondary-border: #cbd5d1;
446 --color-switch-active-track: #1b7d50;
649 --color-switch-active-track: #8f6410;
447650 --color-switch-active-thumb: #fffaff;
448651 --color-switch-inactive-track: #e2ede7;
449652 --color-switch-inactive-thumb: #6e696f;
......@@ -453,7 +656,9 @@
453656 --color-danger: #f4e7e5;
454657 --color-danger-border: rgba(251, 44, 54, 0.32);
455658 --color-danger-foreground: #c10007;
456 --color-input: #ecefed;
659 --color-update: #d0e3da;
660 --color-update-foreground: #125134;
661 --color-input: #f3f7f4;
457662 --color-input-border: #becbc5;
458663 --color-sidebar-search: #d3dcd8;
459664 --color-placeholder: #716971;
......@@ -461,13 +666,15 @@
461666 --color-icon-muted: #746c73;
462667 --color-icon-subtle: #746c73;
463668 --color-header: rgba(243, 247, 244, 0.97);
669 --color-header-foreground: #241523;
464670 --color-header-border: #d5e6dd;
465671 --color-glass-surface: rgba(231, 233, 232, 0.74);
672 --color-glass-fallback: rgba(243, 247, 244, 0.94);
466673 --color-glass-tint: rgba(231, 233, 232, 0.22);
467674 --color-status-bar: #f3f7f4;
468675 --color-md-body: #241523;
469676 --color-md-strong: #241523;
470 --color-md-link: #1b7d50;
677 --color-md-link: #8f6410;
471678 --color-md-blockquote-border: #cbd5d1;
472679 --color-md-blockquote-bg: #e6f0ea;
473680 --color-md-code-bg: #eef1ef;
......@@ -482,7 +689,10 @@
482689 --color-user-bubble-foreground-muted: rgba(36, 21, 35, 0.78);
483690 --color-user-bubble-skill-foreground: #815a0e;
484691 --color-backdrop: rgba(0, 0, 0, 0.22);
485 --color-drawer: rgba(226, 237, 231, 0.99);
692 --color-drawer: #e2ede7;
693 --color-drawer-foreground: #241523;
694 --color-drawer-foreground-muted: #645f64;
695 --color-drawer-border: #cbd3d0;
486696 --color-drawer-shadow: rgba(0, 0, 0, 0.12);
487697 --color-dot-separator: rgba(116, 108, 115, 0.35);
488698 --color-wordmark: #241523;
......@@ -544,32 +754,36 @@
544754 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);
545755 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);
546756 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);
547 --color-clerk-page: #f2f2f7;
548 --color-clerk-foreground: #262626;
549 --color-clerk-foreground-muted: #737373;
550 --color-clerk-border: rgba(229, 229, 234, 0.06);
551 --color-clerk-danger: #dc2626;
757 --color-clerk-page: #fcfcfc;
758 --color-clerk-foreground: #27272a;
759 --color-clerk-foreground-muted: #71717b;
760 --color-clerk-border: #e4e4e7;
761 --color-clerk-danger: #c10007;
552762 }
553763
554764 @variant grove-dark {
555765 --color-screen: #1b2821;
556766 --color-sheet: rgba(27, 40, 33, 0.98);
557767 --color-sheet-solid: #1b2821;
558 --color-card: #36413c;
559 --color-card-alt: #1b2821;
560 --color-card-translucent: rgba(54, 65, 60, 0.8);
768 --color-card: #1b2821;
769 --color-grouped-card: #1b2821;
770 --color-card-alt: #36413c;
771 --color-card-translucent: rgba(27, 40, 33, 0.8);
561772 --color-thread-canvas: #1b2821;
562 --color-thread-selected: #36413c;
773 --color-thread-selected: #2f5641;
563774 --color-thread-selected-foreground: #fffaff;
564 --color-thread-selected-foreground-muted: #919595;
775 --color-thread-selected-foreground-muted: #bbc0bf;
776 --color-thread-hover: #2a4938;
777 --color-row-hover: #325c46;
565778 --color-composer-panel: rgba(27, 40, 33, 0.92);
566 --color-composer-surface: rgba(54, 65, 60, 0.9);
779 --color-composer-surface: rgba(27, 40, 33, 0.9);
567780 --color-composer-border: rgba(65, 95, 79, 0.46);
568781 --color-foreground: #fffaff;
569 --color-foreground-secondary: #919595;
570 --color-foreground-muted: #9da5a2;
782 --color-foreground-secondary: #a6aaaa;
783 --color-foreground-muted: #a3aba8;
571784 --color-foreground-tertiary: #919595;
572785 --color-border: #415f4f;
786 --color-focus: #69d69a;
573787 --color-border-subtle: rgba(65, 95, 79, 0.7);
574788 --color-separator: rgba(65, 95, 79, 0.55);
575789 --color-subtle: #253e31;
......@@ -577,13 +791,14 @@
577791 --color-inline-skill-background: #325c46;
578792 --color-inline-skill-border: rgba(105, 214, 154, 0.42);
579793 --color-inline-skill-foreground: #fffaff;
580 --color-primary: #69d69a;
794 --color-primary: #e3b34e;
581795 --color-primary-foreground: #241523;
796 --color-primary-text: #e3b34e;
582797 --color-primary-shadow: #000000;
583798 --color-secondary: #2a4b39;
584799 --color-secondary-foreground: #fffaff;
585800 --color-secondary-border: #415f4f;
586 --color-switch-active-track: #69d69a;
801 --color-switch-active-track: #e3b34e;
587802 --color-switch-active-thumb: #241523;
588803 --color-switch-inactive-track: #2a4b39;
589804 --color-switch-inactive-thumb: #9da5a2;
......@@ -593,7 +808,9 @@
593808 --color-danger: #3f2c28;
594809 --color-danger-border: rgba(251, 65, 74, 0.32);
595810 --color-danger-foreground: #ff6668;
596 --color-input: #36413c;
811 --color-update: #345f48;
812 --color-update-foreground: #9ee4be;
813 --color-input: #1b2821;
597814 --color-input-border: #4f725f;
598815 --color-sidebar-search: #45554d;
599816 --color-placeholder: #a9abab;
......@@ -601,13 +818,15 @@
601818 --color-icon-muted: #919595;
602819 --color-icon-subtle: #919595;
603820 --color-header: rgba(27, 40, 33, 0.97);
821 --color-header-foreground: #fffaff;
604822 --color-header-border: #36654c;
605823 --color-glass-surface: rgba(68, 77, 73, 0.74);
824 --color-glass-fallback: rgba(27, 40, 33, 0.9);
606825 --color-glass-tint: rgba(68, 77, 73, 0.22);
607826 --color-status-bar: #1b2821;
608827 --color-md-body: #fffaff;
609828 --color-md-strong: #fffaff;
610 --color-md-link: #69d69a;
829 --color-md-link: #e3b34e;
611830 --color-md-blockquote-border: #415f4f;
612831 --color-md-blockquote-bg: #253e31;
613832 --color-md-code-bg: #28342e;
......@@ -622,7 +841,10 @@
622841 --color-user-bubble-foreground-muted: rgba(255, 250, 255, 0.78);
623842 --color-user-bubble-skill-foreground: #eed295;
624843 --color-backdrop: rgba(0, 0, 0, 0.48);
625 --color-drawer: rgba(33, 54, 43, 0.99);
844 --color-drawer: #21362b;
845 --color-drawer-foreground: #fffaff;
846 --color-drawer-foreground-muted: #aab0af;
847 --color-drawer-border: #6f7a75;
626848 --color-drawer-shadow: rgba(0, 0, 0, 0.32);
627849 --color-dot-separator: rgba(145, 149, 149, 0.35);
628850 --color-wordmark: #fffaff;
......@@ -684,32 +906,36 @@
684906 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);
685907 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);
686908 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);
687 --color-clerk-page: #0e0e0e;
909 --color-clerk-page: #0a0a0a;
688910 --color-clerk-foreground: #f5f5f5;
689 --color-clerk-foreground-muted: #a3a3a3;
690 --color-clerk-border: rgba(42, 42, 42, 0.06);
691 --color-clerk-danger: #fca5a5;
911 --color-clerk-foreground-muted: #818181;
912 --color-clerk-border: #191919;
913 --color-clerk-danger: #ff6467;
692914 }
693915
694916 @variant ocean-light {
695917 --color-screen: #f5f7f8;
696918 --color-sheet: rgba(245, 247, 248, 0.98);
697919 --color-sheet-solid: #f5f7f8;
698 --color-card: #edeff1;
699 --color-card-alt: #f5f7f8;
700 --color-card-translucent: rgba(237, 239, 241, 0.8);
920 --color-card: #f5f7f8;
921 --color-grouped-card: #f5f7f8;
922 --color-card-alt: #edeff1;
923 --color-card-translucent: rgba(245, 247, 248, 0.8);
701924 --color-thread-canvas: #f5f7f8;
702 --color-thread-selected: #edeff1;
925 --color-thread-selected: #bed4e5;
703926 --color-thread-selected-foreground: #241523;
704 --color-thread-selected-foreground-muted: #746c75;
927 --color-thread-selected-foreground-muted: #5e5862;
928 --color-thread-hover: #cdddea;
929 --color-row-hover: #d8e4ee;
705930 --color-composer-panel: rgba(245, 247, 248, 0.88);
706 --color-composer-surface: rgba(237, 239, 241, 0.94);
931 --color-composer-surface: rgba(245, 247, 248, 0.94);
707932 --color-composer-border: rgba(205, 212, 220, 0.54);
708933 --color-foreground: #241523;
709 --color-foreground-secondary: #746c75;
934 --color-foreground-secondary: #726a73;
710935 --color-foreground-muted: #6f6873;
711936 --color-foreground-tertiary: #746c75;
712937 --color-border: #cdd4dc;
938 --color-focus: #2672af;
713939 --color-border-subtle: rgba(205, 212, 220, 0.7);
714940 --color-separator: rgba(205, 212, 220, 0.55);
715941 --color-subtle: #e8eff4;
......@@ -717,13 +943,14 @@
717943 --color-inline-skill-background: #d8e4ee;
718944 --color-inline-skill-border: rgba(38, 114, 175, 0.42);
719945 --color-inline-skill-foreground: #241523;
720 --color-primary: #2672af;
946 --color-primary: #0a6f75;
721947 --color-primary-foreground: #fffaff;
948 --color-primary-text: #0a6f75;
722949 --color-primary-shadow: #000000;
723950 --color-secondary: #e4ecf2;
724951 --color-secondary-foreground: #241523;
725952 --color-secondary-border: #cdd4dc;
726 --color-switch-active-track: #2672af;
953 --color-switch-active-track: #0a6f75;
727954 --color-switch-active-thumb: #fffaff;
728955 --color-switch-inactive-track: #e4ecf2;
729956 --color-switch-inactive-thumb: #6f6873;
......@@ -733,7 +960,9 @@
733960 --color-danger: #f5e6e9;
734961 --color-danger-border: rgba(251, 44, 54, 0.32);
735962 --color-danger-foreground: #c10007;
736 --color-input: #edeff1;
963 --color-update: #d4e1ed;
964 --color-update-foreground: #194a72;
965 --color-input: #f5f7f8;
737966 --color-input-border: #c0c9d4;
738967 --color-sidebar-search: #d5dbe2;
739968 --color-placeholder: #716972;
......@@ -741,13 +970,15 @@
741970 --color-icon-muted: #746c75;
742971 --color-icon-subtle: #746c75;
743972 --color-header: rgba(245, 247, 248, 0.97);
973 --color-header-foreground: #241523;
744974 --color-header-border: #d8e4ee;
745975 --color-glass-surface: rgba(232, 233, 235, 0.74);
976 --color-glass-fallback: rgba(245, 247, 248, 0.94);
746977 --color-glass-tint: rgba(232, 233, 235, 0.22);
747978 --color-status-bar: #f5f7f8;
748979 --color-md-body: #241523;
749980 --color-md-strong: #241523;
750 --color-md-link: #2672af;
981 --color-md-link: #0a6f75;
751982 --color-md-blockquote-border: #cdd4dc;
752983 --color-md-blockquote-bg: #e8eff4;
753984 --color-md-code-bg: #f0f1f3;
......@@ -762,7 +993,10 @@
762993 --color-user-bubble-foreground-muted: rgba(36, 21, 35, 0.78);
763994 --color-user-bubble-skill-foreground: #0a6c72;
764995 --color-backdrop: rgba(0, 0, 0, 0.22);
765 --color-drawer: rgba(228, 236, 242, 0.99);
996 --color-drawer: #e4ecf2;
997 --color-drawer-foreground: #241523;
998 --color-drawer-foreground-muted: #655e69;
999 --color-drawer-border: #cdd2d9;
7661000 --color-drawer-shadow: rgba(0, 0, 0, 0.12);
7671001 --color-dot-separator: rgba(116, 108, 117, 0.35);
7681002 --color-wordmark: #241523;
......@@ -824,32 +1058,36 @@
8241058 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);
8251059 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);
8261060 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);
827 --color-clerk-page: #f2f2f7;
828 --color-clerk-foreground: #262626;
829 --color-clerk-foreground-muted: #737373;
830 --color-clerk-border: rgba(229, 229, 234, 0.06);
831 --color-clerk-danger: #dc2626;
1061 --color-clerk-page: #fcfcfc;
1062 --color-clerk-foreground: #27272a;
1063 --color-clerk-foreground-muted: #71717b;
1064 --color-clerk-border: #e4e4e7;
1065 --color-clerk-danger: #c10007;
8321066 }
8331067
8341068 @variant ocean-dark {
8351069 --color-screen: #17212b;
8361070 --color-sheet: rgba(23, 33, 43, 0.98);
8371071 --color-sheet-solid: #17212b;
838 --color-card: #333b45;
839 --color-card-alt: #17212b;
840 --color-card-translucent: rgba(51, 59, 69, 0.8);
1072 --color-card: #17212b;
1073 --color-grouped-card: #17212b;
1074 --color-card-alt: #333b45;
1075 --color-card-translucent: rgba(23, 33, 43, 0.8);
8411076 --color-thread-canvas: #17212b;
842 --color-thread-selected: #333b45;
1077 --color-thread-selected: #2f495f;
8431078 --color-thread-selected-foreground: #fffaff;
844 --color-thread-selected-foreground-muted: #8d8f97;
1079 --color-thread-selected-foreground-muted: #b0b4ba;
1080 --color-thread-hover: #283e50;
1081 --color-row-hover: #324e66;
8451082 --color-composer-panel: rgba(23, 33, 43, 0.92);
846 --color-composer-surface: rgba(51, 59, 69, 0.9);
1083 --color-composer-surface: rgba(23, 33, 43, 0.9);
8471084 --color-composer-border: rgba(64, 85, 103, 0.46);
8481085 --color-foreground: #fffaff;
849 --color-foreground-secondary: #8d8f97;
850 --color-foreground-muted: #969ca6;
1086 --color-foreground-secondary: #a2a3aa;
1087 --color-foreground-muted: #9ea4ad;
8511088 --color-foreground-tertiary: #8d8f97;
8521089 --color-border: #405567;
1090 --color-focus: #70b9ee;
8531091 --color-border-subtle: rgba(64, 85, 103, 0.7);
8541092 --color-separator: rgba(64, 85, 103, 0.55);
8551093 --color-subtle: #233544;
......@@ -857,13 +1095,14 @@
8571095 --color-inline-skill-background: #324e66;
8581096 --color-inline-skill-border: rgba(112, 185, 238, 0.42);
8591097 --color-inline-skill-foreground: #fffaff;
860 --color-primary: #70b9ee;
1098 --color-primary: #5bd0d6;
8611099 --color-primary-foreground: #241523;
1100 --color-primary-text: #5bd0d6;
8621101 --color-primary-shadow: #000000;
8631102 --color-secondary: #293f52;
8641103 --color-secondary-foreground: #fffaff;
8651104 --color-secondary-border: #405567;
866 --color-switch-active-track: #70b9ee;
1105 --color-switch-active-track: #5bd0d6;
8671106 --color-switch-active-thumb: #241523;
8681107 --color-switch-inactive-track: #293f52;
8691108 --color-switch-inactive-thumb: #969ca6;
......@@ -873,7 +1112,9 @@
8731112 --color-danger: #3c2630;
8741113 --color-danger-border: rgba(251, 65, 74, 0.32);
8751114 --color-danger-foreground: #ff6467;
876 --color-input: #333b45;
1115 --color-update: #345269;
1116 --color-update-foreground: #a2d2f4;
1117 --color-input: #17212b;
8771118 --color-input-border: #4f677b;
8781119 --color-sidebar-search: #424e5a;
8791120 --color-placeholder: #a4a4ac;
......@@ -881,13 +1122,15 @@
8811122 --color-icon-muted: #8d8f97;
8821123 --color-icon-subtle: #8d8f97;
8831124 --color-header: rgba(23, 33, 43, 0.97);
1125 --color-header-foreground: #fffaff;
8841126 --color-header-border: #36566f;
8851127 --color-glass-surface: rgba(65, 72, 81, 0.74);
1128 --color-glass-fallback: rgba(23, 33, 43, 0.9);
8861129 --color-glass-tint: rgba(65, 72, 81, 0.22);
8871130 --color-status-bar: #17212b;
8881131 --color-md-body: #fffaff;
8891132 --color-md-strong: #fffaff;
890 --color-md-link: #70b9ee;
1133 --color-md-link: #5bd0d6;
8911134 --color-md-blockquote-border: #405567;
8921135 --color-md-blockquote-bg: #233544;
8931136 --color-md-code-bg: #252e38;
......@@ -902,7 +1145,10 @@
9021145 --color-user-bubble-foreground-muted: rgba(255, 250, 255, 0.78);
9031146 --color-user-bubble-skill-foreground: #75d8dd;
9041147 --color-backdrop: rgba(0, 0, 0, 0.48);
905 --color-drawer: rgba(30, 45, 59, 0.99);
1148 --color-drawer: #1e2d3b;
1149 --color-drawer-foreground: #fffaff;
1150 --color-drawer-foreground-muted: #a2a5ae;
1151 --color-drawer-border: #6d757f;
9061152 --color-drawer-shadow: rgba(0, 0, 0, 0.32);
9071153 --color-dot-separator: rgba(141, 143, 151, 0.35);
9081154 --color-wordmark: #fffaff;
......@@ -964,32 +1210,36 @@
9641210 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);
9651211 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);
9661212 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);
967 --color-clerk-page: #0e0e0e;
1213 --color-clerk-page: #0a0a0a;
9681214 --color-clerk-foreground: #f5f5f5;
969 --color-clerk-foreground-muted: #a3a3a3;
970 --color-clerk-border: rgba(42, 42, 42, 0.06);
971 --color-clerk-danger: #fca5a5;
1215 --color-clerk-foreground-muted: #818181;
1216 --color-clerk-border: #191919;
1217 --color-clerk-danger: #ff6467;
9721218 }
9731219
9741220 @variant ember-light {
9751221 --color-screen: #f9f7f5;
9761222 --color-sheet: rgba(249, 247, 245, 0.98);
9771223 --color-sheet-solid: #f9f7f5;
978 --color-card: #f1efee;
979 --color-card-alt: #f9f7f5;
980 --color-card-translucent: rgba(241, 239, 238, 0.8);
1224 --color-card: #f9f7f5;
1225 --color-grouped-card: #f9f7f5;
1226 --color-card-alt: #f1efee;
1227 --color-card-translucent: rgba(249, 247, 245, 0.8);
9811228 --color-thread-canvas: #f9f7f5;
982 --color-thread-selected: #f1efee;
1229 --color-thread-selected: #e5ccc0;
9831230 --color-thread-selected-foreground: #241523;
984 --color-thread-selected-foreground-muted: #766c74;
1231 --color-thread-selected-foreground-muted: #62575d;
1232 --color-thread-hover: #ead8cf;
1233 --color-row-hover: #eee0d9;
9851234 --color-composer-panel: rgba(249, 247, 245, 0.88);
986 --color-composer-surface: rgba(241, 239, 238, 0.94);
1235 --color-composer-surface: rgba(249, 247, 245, 0.94);
9871236 --color-composer-border: rgba(221, 210, 206, 0.54);
9881237 --color-foreground: #241523;
989 --color-foreground-secondary: #766c74;
1238 --color-foreground-secondary: #746a72;
9901239 --color-foreground-muted: #74686f;
9911240 --color-foreground-tertiary: #766c74;
9921241 --color-border: #ddd2ce;
1242 --color-focus: #ae552a;
9931243 --color-border-subtle: rgba(221, 210, 206, 0.7);
9941244 --color-separator: rgba(221, 210, 206, 0.55);
9951245 --color-subtle: #f4ede9;
......@@ -997,13 +1247,14 @@
9971247 --color-inline-skill-background: #eee0d9;
9981248 --color-inline-skill-border: rgba(174, 85, 42, 0.42);
9991249 --color-inline-skill-foreground: #241523;
1000 --color-primary: #ae552a;
1250 --color-primary: #b23535;
10011251 --color-primary-foreground: #fffaff;
1252 --color-primary-text: #b23535;
10021253 --color-primary-shadow: #000000;
10031254 --color-secondary: #f3eae5;
10041255 --color-secondary-foreground: #241523;
10051256 --color-secondary-border: #ddd2ce;
1006 --color-switch-active-track: #ae552a;
1257 --color-switch-active-track: #b23535;
10071258 --color-switch-active-thumb: #fffaff;
10081259 --color-switch-inactive-track: #f3eae5;
10091260 --color-switch-inactive-thumb: #74686f;
......@@ -1013,7 +1264,9 @@
10131264 --color-danger: #f9e7e6;
10141265 --color-danger-border: rgba(251, 44, 54, 0.32);
10151266 --color-danger-foreground: #c10007;
1016 --color-input: #f1efee;
1267 --color-update: #edddd5;
1268 --color-update-foreground: #71381b;
1269 --color-input: #f9f7f5;
10171270 --color-input-border: #d4c6c1;
10181271 --color-sidebar-search: #e2d9d6;
10191272 --color-placeholder: #736971;
......@@ -1021,13 +1274,15 @@
10211274 --color-icon-muted: #766c74;
10221275 --color-icon-subtle: #766c74;
10231276 --color-header: rgba(249, 247, 245, 0.97);
1277 --color-header-foreground: #241523;
10241278 --color-header-border: #eee0d9;
10251279 --color-glass-surface: rgba(236, 233, 233, 0.74);
1280 --color-glass-fallback: rgba(249, 247, 245, 0.94);
10261281 --color-glass-tint: rgba(236, 233, 233, 0.22);
10271282 --color-status-bar: #f9f7f5;
10281283 --color-md-body: #241523;
10291284 --color-md-strong: #241523;
1030 --color-md-link: #ae552a;
1285 --color-md-link: #b23535;
10311286 --color-md-blockquote-border: #ddd2ce;
10321287 --color-md-blockquote-bg: #f4ede9;
10331288 --color-md-code-bg: #f3f1f0;
......@@ -1042,7 +1297,10 @@
10421297 --color-user-bubble-foreground-muted: rgba(36, 21, 35, 0.78);
10431298 --color-user-bubble-skill-foreground: #b13535;
10441299 --color-backdrop: rgba(0, 0, 0, 0.22);
1045 --color-drawer: rgba(243, 234, 229, 0.99);
1300 --color-drawer: #f3eae5;
1301 --color-drawer-foreground: #241523;
1302 --color-drawer-foreground-muted: #6a5d64;
1303 --color-drawer-border: #dad0ce;
10461304 --color-drawer-shadow: rgba(0, 0, 0, 0.12);
10471305 --color-dot-separator: rgba(118, 108, 116, 0.35);
10481306 --color-wordmark: #241523;
......@@ -1104,32 +1362,36 @@
11041362 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);
11051363 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);
11061364 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);
1107 --color-clerk-page: #f2f2f7;
1108 --color-clerk-foreground: #262626;
1109 --color-clerk-foreground-muted: #737373;
1110 --color-clerk-border: rgba(229, 229, 234, 0.06);
1111 --color-clerk-danger: #dc2626;
1365 --color-clerk-page: #fcfcfc;
1366 --color-clerk-foreground: #27272a;
1367 --color-clerk-foreground-muted: #71717b;
1368 --color-clerk-border: #e4e4e7;
1369 --color-clerk-danger: #c10007;
11121370 }
11131371
11141372 @variant ember-dark {
11151373 --color-screen: #291e1a;
11161374 --color-sheet: rgba(41, 30, 26, 0.98);
11171375 --color-sheet-solid: #291e1a;
1118 --color-card: #433835;
1119 --color-card-alt: #291e1a;
1120 --color-card-translucent: rgba(67, 56, 53, 0.8);
1376 --color-card: #291e1a;
1377 --color-grouped-card: #291e1a;
1378 --color-card-alt: #433835;
1379 --color-card-translucent: rgba(41, 30, 26, 0.8);
11211380 --color-thread-canvas: #291e1a;
1122 --color-thread-selected: #433835;
1381 --color-thread-selected: #5d3f2d;
11231382 --color-thread-selected-foreground: #fffaff;
1124 --color-thread-selected-foreground-muted: #968e8f;
1383 --color-thread-selected-foreground-muted: #b9b1b0;
1384 --color-thread-hover: #4f3528;
1385 --color-row-hover: #644330;
11251386 --color-composer-panel: rgba(41, 30, 26, 0.92);
1126 --color-composer-surface: rgba(67, 56, 53, 0.9);
1387 --color-composer-surface: rgba(41, 30, 26, 0.9);
11271388 --color-composer-border: rgba(102, 76, 63, 0.46);
11281389 --color-foreground: #fffaff;
1129 --color-foreground-secondary: #968e8f;
1130 --color-foreground-muted: #a59996;
1390 --color-foreground-secondary: #a8a2a2;
1391 --color-foreground-muted: #aca19f;
11311392 --color-foreground-tertiary: #968e8f;
11321393 --color-border: #664c3f;
1394 --color-focus: #f09a64;
11331395 --color-border-subtle: rgba(102, 76, 63, 0.7);
11341396 --color-separator: rgba(102, 76, 63, 0.55);
11351397 --color-subtle: #432e23;
......@@ -1137,13 +1399,14 @@
11371399 --color-inline-skill-background: #644330;
11381400 --color-inline-skill-border: rgba(240, 154, 100, 0.42);
11391401 --color-inline-skill-foreground: #fffaff;
1140 --color-primary: #f09a64;
1402 --color-primary: #f78a7a;
11411403 --color-primary-foreground: #241523;
1404 --color-primary-text: #f78a7a;
11421405 --color-primary-shadow: #000000;
11431406 --color-secondary: #513728;
11441407 --color-secondary-foreground: #fffaff;
11451408 --color-secondary-border: #664c3f;
1146 --color-switch-active-track: #f09a64;
1409 --color-switch-active-track: #f78a7a;
11471410 --color-switch-active-thumb: #241523;
11481411 --color-switch-inactive-track: #513728;
11491412 --color-switch-inactive-thumb: #a59996;
......@@ -1153,7 +1416,9 @@
11531416 --color-danger: #4a2321;
11541417 --color-danger-border: rgba(251, 65, 74, 0.32);
11551418 --color-danger-foreground: #ff6467;
1156 --color-input: #433835;
1419 --color-update: #684631;
1420 --color-update-foreground: #f5bd9a;
1421 --color-input: #291e1a;
11571422 --color-input-border: #7a5d4d;
11581423 --color-sidebar-search: #584943;
11591424 --color-placeholder: #aba3a5;
......@@ -1161,13 +1426,15 @@
11611426 --color-icon-muted: #968e8f;
11621427 --color-icon-subtle: #968e8f;
11631428 --color-header: rgba(41, 30, 26, 0.97);
1429 --color-header-foreground: #fffaff;
11641430 --color-header-border: #6e4934;
11651431 --color-glass-surface: rgba(79, 69, 67, 0.74);
1432 --color-glass-fallback: rgba(41, 30, 26, 0.9);
11661433 --color-glass-tint: rgba(79, 69, 67, 0.22);
11671434 --color-status-bar: #291e1a;
11681435 --color-md-body: #fffaff;
11691436 --color-md-strong: #fffaff;
1170 --color-md-link: #f09a64;
1437 --color-md-link: #f78a7a;
11711438 --color-md-blockquote-border: #664c3f;
11721439 --color-md-blockquote-bg: #432e23;
11731440 --color-md-code-bg: #362b27;
......@@ -1182,7 +1449,10 @@
11821449 --color-user-bubble-foreground-muted: rgba(255, 250, 255, 0.78);
11831450 --color-user-bubble-skill-foreground: #fab6ad;
11841451 --color-backdrop: rgba(0, 0, 0, 0.48);
1185 --color-drawer: rgba(57, 40, 31, 0.99);
1452 --color-drawer: #39281f;
1453 --color-drawer-foreground: #fffaff;
1454 --color-drawer-foreground-muted: #aca2a1;
1455 --color-drawer-border: #7e716e;
11861456 --color-drawer-shadow: rgba(0, 0, 0, 0.32);
11871457 --color-dot-separator: rgba(150, 142, 143, 0.35);
11881458 --color-wordmark: #fffaff;
......@@ -1244,32 +1514,36 @@
12441514 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);
12451515 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);
12461516 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);
1247 --color-clerk-page: #0e0e0e;
1517 --color-clerk-page: #0a0a0a;
12481518 --color-clerk-foreground: #f5f5f5;
1249 --color-clerk-foreground-muted: #a3a3a3;
1250 --color-clerk-border: rgba(42, 42, 42, 0.06);
1251 --color-clerk-danger: #fca5a5;
1519 --color-clerk-foreground-muted: #818181;
1520 --color-clerk-border: #191919;
1521 --color-clerk-danger: #ff6467;
12521522 }
12531523
12541524 @variant iris-light {
12551525 --color-screen: #f8f7f9;
12561526 --color-sheet: rgba(248, 247, 249, 0.98);
12571527 --color-sheet-solid: #f8f7f9;
1258 --color-card: #f0eff2;
1259 --color-card-alt: #f8f7f9;
1260 --color-card-translucent: rgba(240, 239, 242, 0.8);
1528 --color-card: #f8f7f9;
1529 --color-grouped-card: #f8f7f9;
1530 --color-card-alt: #f0eff2;
1531 --color-card-translucent: rgba(248, 247, 249, 0.8);
12611532 --color-thread-canvas: #f8f7f9;
1262 --color-thread-selected: #f0eff2;
1533 --color-thread-selected: #d4cce8;
12631534 --color-thread-selected-foreground: #241523;
1264 --color-thread-selected-foreground-muted: #766c76;
1535 --color-thread-selected-foreground-muted: #605662;
1536 --color-thread-hover: #ded8ed;
1537 --color-row-hover: #e5e0f0;
12651538 --color-composer-panel: rgba(248, 247, 249, 0.88);
1266 --color-composer-surface: rgba(240, 239, 242, 0.94);
1539 --color-composer-surface: rgba(248, 247, 249, 0.94);
12671540 --color-composer-border: rgba(214, 209, 222, 0.54);
12681541 --color-foreground: #241523;
1269 --color-foreground-secondary: #766c76;
1542 --color-foreground-secondary: #746a74;
12701543 --color-foreground-muted: #726874;
12711544 --color-foreground-tertiary: #766c76;
12721545 --color-border: #d6d1de;
1546 --color-focus: #7253b9;
12731547 --color-border-subtle: rgba(214, 209, 222, 0.7);
12741548 --color-separator: rgba(214, 209, 222, 0.55);
12751549 --color-subtle: #f0edf6;
......@@ -1277,13 +1551,14 @@
12771551 --color-inline-skill-background: #e5e0f0;
12781552 --color-inline-skill-border: rgba(114, 83, 185, 0.42);
12791553 --color-inline-skill-foreground: #241523;
1280 --color-primary: #7253b9;
1554 --color-primary: #a82c87;
12811555 --color-primary-foreground: #fffaff;
1556 --color-primary-text: #a82c87;
12821557 --color-primary-shadow: #000000;
12831558 --color-secondary: #edeaf4;
12841559 --color-secondary-foreground: #241523;
12851560 --color-secondary-border: #d6d1de;
1286 --color-switch-active-track: #7253b9;
1561 --color-switch-active-track: #a82c87;
12871562 --color-switch-active-thumb: #fffaff;
12881563 --color-switch-inactive-track: #edeaf4;
12891564 --color-switch-inactive-thumb: #726874;
......@@ -1293,7 +1568,9 @@
12931568 --color-danger: #f8e6ea;
12941569 --color-danger-border: rgba(251, 44, 54, 0.32);
12951570 --color-danger-foreground: #c10007;
1296 --color-input: #f0eff2;
1571 --color-update: #e2ddef;
1572 --color-update-foreground: #4a3678;
1573 --color-input: #f8f7f9;
12971574 --color-input-border: #ccc5d6;
12981575 --color-sidebar-search: #ddd9e3;
12991576 --color-placeholder: #736973;
......@@ -1301,13 +1578,15 @@
13011578 --color-icon-muted: #766c76;
13021579 --color-icon-subtle: #766c76;
13031580 --color-header: rgba(248, 247, 249, 0.97);
1581 --color-header-foreground: #241523;
13041582 --color-header-border: #e5e0f0;
13051583 --color-glass-surface: rgba(235, 233, 237, 0.74);
1584 --color-glass-fallback: rgba(248, 247, 249, 0.94);
13061585 --color-glass-tint: rgba(235, 233, 237, 0.22);
13071586 --color-status-bar: #f8f7f9;
13081587 --color-md-body: #241523;
13091588 --color-md-strong: #241523;
1310 --color-md-link: #7253b9;
1589 --color-md-link: #a82c87;
13111590 --color-md-blockquote-border: #d6d1de;
13121591 --color-md-blockquote-bg: #f0edf6;
13131592 --color-md-code-bg: #f2f1f4;
......@@ -1322,7 +1601,10 @@
13221601 --color-user-bubble-foreground-muted: rgba(36, 21, 35, 0.78);
13231602 --color-user-bubble-skill-foreground: #a82c87;
13241603 --color-backdrop: rgba(0, 0, 0, 0.22);
1325 --color-drawer: rgba(237, 234, 244, 0.99);
1604 --color-drawer: #edeaf4;
1605 --color-drawer-foreground: #241523;
1606 --color-drawer-foreground-muted: #685d6a;
1607 --color-drawer-border: #d5d0db;
13261608 --color-drawer-shadow: rgba(0, 0, 0, 0.12);
13271609 --color-dot-separator: rgba(118, 108, 118, 0.35);
13281610 --color-wordmark: #241523;
......@@ -1384,32 +1666,36 @@
13841666 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);
13851667 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);
13861668 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);
1387 --color-clerk-page: #f2f2f7;
1388 --color-clerk-foreground: #262626;
1389 --color-clerk-foreground-muted: #737373;
1390 --color-clerk-border: rgba(229, 229, 234, 0.06);
1391 --color-clerk-danger: #dc2626;
1669 --color-clerk-page: #fcfcfc;
1670 --color-clerk-foreground: #27272a;
1671 --color-clerk-foreground-muted: #71717b;
1672 --color-clerk-border: #e4e4e7;
1673 --color-clerk-danger: #c10007;
13921674 }
13931675
13941676 @variant iris-dark {
13951677 --color-screen: #1d1929;
13961678 --color-sheet: rgba(29, 25, 41, 0.98);
13971679 --color-sheet-solid: #1d1929;
1398 --color-card: #383443;
1399 --color-card-alt: #1d1929;
1400 --color-card-translucent: rgba(56, 52, 67, 0.8);
1680 --color-card: #1d1929;
1681 --color-grouped-card: #1d1929;
1682 --color-card-alt: #383443;
1683 --color-card-translucent: rgba(29, 25, 41, 0.8);
14011684 --color-thread-canvas: #1d1929;
1402 --color-thread-selected: #383443;
1685 --color-thread-selected: #3f345e;
14031686 --color-thread-selected-foreground: #fffaff;
1404 --color-thread-selected-foreground-muted: #8e8a95;
1687 --color-thread-selected-foreground-muted: #a6a2ae;
1688 --color-thread-hover: #352c4f;
1689 --color-row-hover: #433765;
14051690 --color-composer-panel: rgba(29, 25, 41, 0.92);
1406 --color-composer-surface: rgba(56, 52, 67, 0.9);
1691 --color-composer-surface: rgba(29, 25, 41, 0.9);
14071692 --color-composer-border: rgba(77, 67, 102, 0.46);
14081693 --color-foreground: #fffaff;
1409 --color-foreground-secondary: #8e8a95;
1410 --color-foreground-muted: #9690a1;
1694 --color-foreground-secondary: #a09da6;
1695 --color-foreground-muted: #a19cab;
14111696 --color-foreground-tertiary: #8e8a95;
14121697 --color-border: #4d4366;
1698 --color-focus: #9d7df2;
14131699 --color-border-subtle: rgba(77, 67, 102, 0.7);
14141700 --color-separator: rgba(77, 67, 102, 0.55);
14151701 --color-subtle: #2d2643;
......@@ -1417,13 +1703,14 @@
14171703 --color-inline-skill-background: #433765;
14181704 --color-inline-skill-border: rgba(157, 125, 242, 0.42);
14191705 --color-inline-skill-foreground: #fffaff;
1420 --color-primary: #9d7df2;
1706 --color-primary: #f099d8;
14211707 --color-primary-foreground: #241523;
1708 --color-primary-text: #f099d8;
14221709 --color-primary-shadow: #000000;
14231710 --color-secondary: #362d51;
14241711 --color-secondary-foreground: #fffaff;
14251712 --color-secondary-border: #4d4366;
1426 --color-switch-active-track: #9d7df2;
1713 --color-switch-active-track: #f099d8;
14271714 --color-switch-active-thumb: #241523;
14281715 --color-switch-inactive-track: #362d51;
14291716 --color-switch-inactive-thumb: #9690a1;
......@@ -1433,7 +1720,9 @@
14331720 --color-danger: #40202e;
14341721 --color-danger-border: rgba(251, 65, 74, 0.32);
14351722 --color-danger-foreground: #ff6467;
1436 --color-input: #383443;
1723 --color-update: #463969;
1724 --color-update-foreground: #bfabf7;
1725 --color-input: #1d1929;
14371726 --color-input-border: #5d527b;
14381727 --color-sidebar-search: #494459;
14391728 --color-placeholder: #a29ea8;
......@@ -1441,13 +1730,15 @@
14411730 --color-icon-muted: #8e8a95;
14421731 --color-icon-subtle: #8e8a95;
14431732 --color-header: rgba(29, 25, 41, 0.97);
1733 --color-header-foreground: #fffaff;
14441734 --color-header-border: #4a3c70;
14451735 --color-glass-surface: rgba(69, 66, 80, 0.74);
1736 --color-glass-fallback: rgba(29, 25, 41, 0.9);
14461737 --color-glass-tint: rgba(69, 66, 80, 0.22);
14471738 --color-status-bar: #1d1929;
14481739 --color-md-body: #fffaff;
14491740 --color-md-strong: #fffaff;
1450 --color-md-link: #9d7df2;
1741 --color-md-link: #f099d8;
14511742 --color-md-blockquote-border: #4d4366;
14521743 --color-md-blockquote-bg: #2d2643;
14531744 --color-md-code-bg: #2a2736;
......@@ -1462,7 +1753,10 @@
14621753 --color-user-bubble-foreground-muted: rgba(255, 250, 255, 0.78);
14631754 --color-user-bubble-skill-foreground: #f099d8;
14641755 --color-backdrop: rgba(0, 0, 0, 0.48);
1465 --color-drawer: rgba(39, 33, 57, 0.99);
1756 --color-drawer: #272139;
1757 --color-drawer-foreground: #fffaff;
1758 --color-drawer-foreground-muted: #9b97a4;
1759 --color-drawer-border: #736d7e;
14661760 --color-drawer-shadow: rgba(0, 0, 0, 0.32);
14671761 --color-dot-separator: rgba(142, 138, 149, 0.35);
14681762 --color-wordmark: #fffaff;
......@@ -1524,11 +1818,11 @@
15241818 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);
15251819 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);
15261820 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);
1527 --color-clerk-page: #0e0e0e;
1821 --color-clerk-page: #0a0a0a;
15281822 --color-clerk-foreground: #f5f5f5;
1529 --color-clerk-foreground-muted: #a3a3a3;
1530 --color-clerk-border: rgba(42, 42, 42, 0.06);
1531 --color-clerk-danger: #fca5a5;
1823 --color-clerk-foreground-muted: #818181;
1824 --color-clerk-border: #191919;
1825 --color-clerk-danger: #ff6467;
15321826 }
15331827 }
15341828}
apps/mobile/global.css-222
......@@ -8,228 +8,6 @@
88 @variant android {
99 --font-mono: "monospace";
1010 }
11
12 @variant light {
13 /* Page backgrounds */
14 --color-screen: #f2f2f7;
15 --color-sheet: rgba(242, 242, 247, 0.98);
16 --color-sheet-solid: #f2f2f7;
17
18 /* Card / surface */
19 --color-card: #ffffff;
20 --color-card-alt: #f5f5f5;
21 --color-card-translucent: rgba(255, 255, 255, 0.8);
22 --color-thread-canvas: #f5f5f5;
23 --color-thread-selected: #ffffff;
24 --color-thread-selected-foreground: #262626;
25 --color-thread-selected-foreground-muted: #737373;
26 --color-composer-panel: rgba(245, 245, 245, 0.88);
27 --color-composer-surface: rgba(255, 255, 255, 0.94);
28 --color-composer-border: rgba(0, 0, 0, 0.1);
29
30 /* Text */
31 --color-foreground: #262626;
32 --color-foreground-secondary: #525252;
33 --color-foreground-muted: #737373;
34 --color-foreground-tertiary: #8e8e93;
35
36 /* Borders & separators */
37 --color-border: rgba(0, 0, 0, 0.08);
38 --color-border-subtle: rgba(0, 0, 0, 0.06);
39 --color-separator: rgba(0, 0, 0, 0.04);
40
41 /* Subtle backgrounds (badges, pills, overlays) */
42 --color-subtle: rgba(0, 0, 0, 0.04);
43 --color-subtle-strong: rgba(0, 0, 0, 0.08);
44 --color-inline-skill-background: rgba(217, 70, 239, 0.12);
45 --color-inline-skill-border: rgba(217, 70, 239, 0.25);
46 --color-inline-skill-foreground: #a21caf;
47
48 /* Primary action */
49 --color-primary: #262626;
50 --color-primary-foreground: #ffffff;
51 --color-primary-shadow: #000000;
52
53 /* Secondary action */
54 --color-secondary: #ffffff;
55 --color-secondary-foreground: #262626;
56 --color-secondary-border: rgba(0, 0, 0, 0.08);
57 --color-switch-active-track: #34c759;
58 --color-switch-active-thumb: #ffffff;
59 --color-switch-inactive-track: rgba(0, 0, 0, 0.08);
60 --color-switch-inactive-thumb: #8e8e93;
61
62 /* Warning */
63 --color-warning: #fffbeb;
64 --color-warning-border: #fde68a;
65 --color-warning-foreground: #b45309;
66
67 /* Danger */
68 --color-danger: #fef2f2;
69 --color-danger-border: rgba(239, 68, 68, 0.12);
70 --color-danger-foreground: #dc2626;
71
72 /* Inputs */
73 --color-input: #ffffff;
74 --color-input-border: rgba(0, 0, 0, 0.1);
75 --color-sidebar-search: rgba(118, 118, 128, 0.12);
76 --color-placeholder: #737373;
77
78 /* Icons */
79 --color-icon: #262626;
80 --color-icon-muted: #525252;
81 --color-icon-subtle: #a3a3a3;
82
83 /* Header / glass chrome */
84 --color-header: rgba(255, 255, 255, 0.97);
85 --color-header-border: rgba(0, 0, 0, 0.06);
86 --color-glass-surface: rgba(255, 255, 255, 0.72);
87 --color-glass-tint: rgba(255, 255, 255, 0.18);
88
89 /* StatusBar */
90 --color-status-bar: #f2f2f7;
91
92 /* Markdown */
93 --color-md-body: #111111;
94 --color-md-strong: #000000;
95 --color-md-link: #2563eb;
96 --color-md-blockquote-border: rgba(0, 0, 0, 0.08);
97 --color-md-blockquote-bg: rgba(0, 0, 0, 0.02);
98 --color-md-code-bg: rgba(0, 0, 0, 0.04);
99 --color-md-code-text: #262626;
100 --color-md-user-code-bg: rgba(0, 0, 0, 0.04);
101 --color-md-user-code-text: #262626;
102 --color-md-user-fence-bg: rgba(0, 0, 0, 0.06);
103 --color-md-user-fence-text: #262626;
104 --color-md-hr: rgba(0, 0, 0, 0.08);
105
106 /* User bubble: a raised card on the thread canvas, matching web's message surface */
107 --color-user-bubble: #ffffff;
108 --color-user-bubble-foreground: #262626;
109 --color-user-bubble-foreground-muted: rgba(38, 38, 38, 0.78);
110 --color-user-bubble-skill-foreground: #2563eb;
111
112 /* Drawer / modal backdrop */
113 --color-backdrop: rgba(0, 0, 0, 0.22);
114 --color-drawer: rgba(255, 255, 255, 0.99);
115 --color-drawer-shadow: rgba(0, 0, 0, 0.12);
116
117 /* Misc */
118 --color-dot-separator: rgba(0, 0, 0, 0.2);
119 --color-wordmark: #262626;
120 --color-chevron: rgba(0, 0, 0, 0.2);
121 }
122
123 @variant dark {
124 /* Page backgrounds */
125 --color-screen: #0a0a0a;
126 --color-sheet: rgba(14, 14, 14, 0.98);
127 --color-sheet-solid: #0e0e0e;
128
129 /* Card / surface */
130 --color-card: #171717;
131 --color-card-alt: #1c1c1c;
132 --color-card-translucent: rgba(17, 17, 17, 0.8);
133 --color-thread-canvas: #1c1c1c;
134 --color-thread-selected: #171717;
135 --color-thread-selected-foreground: #f5f5f5;
136 --color-thread-selected-foreground-muted: #8e8e93;
137 --color-composer-panel: rgba(28, 28, 28, 0.92);
138 --color-composer-surface: rgba(23, 23, 23, 0.9);
139 --color-composer-border: rgba(255, 255, 255, 0.08);
140
141 /* Text */
142 --color-foreground: #f5f5f5;
143 --color-foreground-secondary: #a3a3a3;
144 --color-foreground-muted: #8e8e93;
145 --color-foreground-tertiary: #636366;
146
147 /* Borders & separators */
148 --color-border: rgba(255, 255, 255, 0.06);
149 --color-border-subtle: rgba(255, 255, 255, 0.04);
150 --color-separator: rgba(255, 255, 255, 0.03);
151
152 /* Subtle backgrounds (badges, pills, overlays) */
153 --color-subtle: rgba(255, 255, 255, 0.04);
154 --color-subtle-strong: rgba(255, 255, 255, 0.08);
155 --color-inline-skill-background: rgba(217, 70, 239, 0.12);
156 --color-inline-skill-border: rgba(217, 70, 239, 0.25);
157 --color-inline-skill-foreground: #f0abfc;
158
159 /* Primary action */
160 --color-primary: #f5f5f5;
161 --color-primary-foreground: #0a0a0a;
162 --color-primary-shadow: #000000;
163
164 /* Secondary action */
165 --color-secondary: rgba(255, 255, 255, 0.04);
166 --color-secondary-foreground: #f5f5f5;
167 --color-secondary-border: rgba(255, 255, 255, 0.06);
168 --color-switch-active-track: #30d158;
169 --color-switch-active-thumb: #ffffff;
170 --color-switch-inactive-track: rgba(255, 255, 255, 0.06);
171 --color-switch-inactive-thumb: #8e8e93;
172
173 /* Warning */
174 --color-warning: rgba(69, 26, 3, 0.4);
175 --color-warning-border: rgba(120, 53, 15, 0.6);
176 --color-warning-foreground: #fcd34d;
177
178 /* Danger */
179 --color-danger: rgba(239, 68, 68, 0.14);
180 --color-danger-border: rgba(248, 113, 113, 0.18);
181 --color-danger-foreground: #fca5a5;
182
183 /* Inputs */
184 --color-input: #141414;
185 --color-input-border: rgba(255, 255, 255, 0.08);
186 --color-sidebar-search: rgba(118, 118, 128, 0.24);
187 --color-placeholder: #8e8e93;
188
189 /* Icons */
190 --color-icon: #f5f5f5;
191 --color-icon-muted: #a3a3a3;
192 --color-icon-subtle: #8e8e93;
193
194 /* Header / glass chrome */
195 --color-header: rgba(10, 10, 10, 0.97);
196 --color-header-border: rgba(255, 255, 255, 0.06);
197 --color-glass-surface: rgba(23, 23, 23, 0.78);
198 --color-glass-tint: rgba(23, 23, 23, 0.24);
199
200 /* StatusBar */
201 --color-status-bar: #0a0a0a;
202
203 /* Markdown */
204 --color-md-body: #e5e5e5;
205 --color-md-strong: #f5f5f5;
206 --color-md-link: #60a5fa;
207 --color-md-blockquote-border: rgba(255, 255, 255, 0.1);
208 --color-md-blockquote-bg: rgba(255, 255, 255, 0.03);
209 --color-md-code-bg: rgba(255, 255, 255, 0.06);
210 --color-md-code-text: #e5e5e5;
211 --color-md-user-code-bg: rgba(255, 255, 255, 0.06);
212 --color-md-user-code-text: #e5e5e5;
213 --color-md-user-fence-bg: rgba(255, 255, 255, 0.09);
214 --color-md-user-fence-text: #e5e5e5;
215 --color-md-hr: rgba(255, 255, 255, 0.08);
216
217 /* User bubble: a raised card on the thread canvas, matching web's message surface */
218 --color-user-bubble: #171717;
219 --color-user-bubble-foreground: #f5f5f5;
220 --color-user-bubble-foreground-muted: rgba(245, 245, 245, 0.78);
221 --color-user-bubble-skill-foreground: #60a5fa;
222
223 /* Drawer / modal backdrop */
224 --color-backdrop: rgba(0, 0, 0, 0.48);
225 --color-drawer: rgba(14, 14, 14, 0.99);
226 --color-drawer-shadow: rgba(0, 0, 0, 0.32);
227
228 /* Misc */
229 --color-dot-separator: rgba(255, 255, 255, 0.2);
230 --color-wordmark: #f5f5f5;
231 --color-chevron: rgba(255, 255, 255, 0.2);
232 }
23311 }
23412}
23513
apps/mobile/modules/t3-markdown-text/android/src/main/java/expo/modules/t3markdowntext/T3ContextChip.kt+9-1
......@@ -114,7 +114,15 @@ class T3ContextChip(
114114 fun color(
115115 value: String,
116116 fallback: Int
117 ): Int = runCatching { Color.parseColor(value) }.getOrDefault(fallback)
117 ): Int = runCatching {
118 // React Native hex colors put alpha last; Android's parser puts it first.
119 val androidColor = if (value.length == 9 && value.startsWith("#")) {
120 "#${value.takeLast(2)}${value.substring(1, 7)}"
121 } else {
122 value
123 }
124 Color.parseColor(androidColor)
125 }.getOrDefault(fallback)
118126
119127 private fun blend(accent: Int, base: Int, weight: Float): Int = Color.rgb(
120128 (Color.red(accent) * weight + Color.red(base) * (1 - weight)).toInt(),
apps/mobile/plugins/withAndroidModernAlertDialog.cjs+15-16
......@@ -1,5 +1,6 @@
11const fs = require("node:fs");
22const path = require("node:path");
3const themeVariables = require("../generated-uniwind-default-theme-variables.json");
34const {
45 AndroidConfig,
56 withAndroidColors,
......@@ -11,9 +12,10 @@ const {
1112// React Native's Alert renders an AppCompat AlertDialog on Android, which
1213// inherits the dated framework dialog chrome (square gray panel, teal
1314// all-caps buttons) from the app theme. These resources restyle it with the
14// app's uniwind tokens from global.css: --color-card panel, --color-foreground
15// text, --color-primary buttons, DM Sans type. The @font resources referenced
16// here are embedded by the expo-font plugin config in app.config.ts.
15// generated default palette: card panel, foreground text, readable primary
16// buttons, DM Sans type. Alert exposes no runtime custom-palette API, so these
17// build-time resources use the stock palette for each native appearance.
18// The fonts are embedded by the expo-font plugin config in app.config.ts.
1719
1820// AppCompat's default dialog window background is an inset rounded rect, so
1921// the replacement keeps the same 16dp inset to preserve the dialog's margins.
......@@ -30,21 +32,18 @@ const DIALOG_BACKGROUND_DRAWABLE = `<?xml version="1.0" encoding="utf-8"?>
3032</inset>
3133`;
3234
33const COLORS = {
34 light: {
35 background: "#FFFFFF", // --color-card
36 text: "#262626", // --color-foreground
37 secondaryText: "#525252", // --color-foreground-secondary
38 buttonText: "#262626", // --color-primary
39 },
40 night: {
41 background: "#171717",
42 text: "#F5F5F5",
43 secondaryText: "#A3A3A3",
44 buttonText: "#F5F5F5",
45 },
35const colorsFor = (appearance) => {
36 const variables = themeVariables[appearance];
37 return {
38 background: variables["--color-card"],
39 text: variables["--color-foreground"],
40 secondaryText: variables["--color-foreground-secondary"],
41 buttonText: variables["--color-primary-text"],
42 };
4643};
4744
45const COLORS = { light: colorsFor("light"), night: colorsFor("dark") };
46
4847function assignStyleItem(style, name, value) {
4948 style.item = style.item ?? [];
5049 const existing = style.item.find((item) => item.$?.name === name);
apps/mobile/scripts/generate-uniwind-themes.mts+46-44
......@@ -5,17 +5,15 @@ import * as NodePath from "node:path";
55import tailwindColors from "tailwindcss/colors";
66import { BUILT_IN_THEME_IDS, type BuiltInThemeId } from "@t3tools/shared/themePalettes";
77
8import clerkTheme from "../clerk-theme.json" with { type: "json" };
98import {
9 createMobileThemeVariables,
10 getMobileThemeColors,
1011 getMobileThemeVariables,
11 MOBILE_THEME_VARIABLE_NAMES,
12 themeColorWithAlpha,
12 DEFAULT_MOBILE_THEME_ID,
1313 type MobileThemeAppearance,
14 type MobileThemeVariables,
1514} from "../src/lib/mobileTheme.ts";
1615
1716const APPEARANCES = ["light", "dark"] as const;
18const GLOBAL_CSS_PATH = NodePath.resolve(import.meta.dirname, "../global.css");
1917const GENERATED_CSS_PATH = NodePath.resolve(import.meta.dirname, "../generated-uniwind-themes.css");
2018const GENERATED_NAMES_PATH = NodePath.resolve(
2119 import.meta.dirname,
......@@ -25,6 +23,7 @@ const GENERATED_DEFAULT_VARIABLES_PATH = NodePath.resolve(
2523 import.meta.dirname,
2624 "../generated-uniwind-default-theme-variables.json",
2725);
26const GENERATED_CLERK_THEME_PATH = NodePath.resolve(import.meta.dirname, "../clerk-theme.json");
2827
2928type TailwindColorFamily = keyof typeof tailwindColors;
3029type TailwindColorShade = 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950;
......@@ -159,13 +158,38 @@ const variablesFor = (themeId: BuiltInThemeId, appearance: MobileThemeAppearance
159158
160159// Clerk's native screens use one build-time palette per appearance. Custom
161160// profile pages must match it even when the rest of the app uses a named theme.
161const clerkColorsFor = (appearance: MobileThemeAppearance) => {
162 // Native authentication uses plain cards, rather than tonal settings groups.
163 const variables = createMobileThemeVariables(
164 getMobileThemeColors(DEFAULT_MOBILE_THEME_ID, appearance),
165 appearance,
166 );
167 return {
168 primary: variables["--color-primary"],
169 background: variables["--color-sheet-solid"],
170 input: variables["--color-input"],
171 danger: variables["--color-danger-foreground"],
172 success: appearance === "dark" ? "#34d399" : "#059669",
173 warning: variables["--color-warning-foreground"],
174 foreground: variables["--color-foreground"],
175 mutedForeground: variables["--color-foreground-muted"],
176 primaryForeground: variables["--color-primary-foreground"],
177 inputForeground: variables["--color-foreground"],
178 neutral: variables["--color-secondary"],
179 border: variables["--color-border"],
180 ring: variables["--color-focus"],
181 muted: variables["--color-subtle"],
182 shadow: variables["--color-primary-shadow"],
183 };
184};
185
162186const clerkVariablesFor = (appearance: MobileThemeAppearance) => {
163 const colors = appearance === "dark" ? clerkTheme.darkColors : clerkTheme.colors;
187 const colors = clerkColorsFor(appearance);
164188 return {
165189 "--color-clerk-page": colors.background.toLowerCase(),
166190 "--color-clerk-foreground": colors.foreground.toLowerCase(),
167191 "--color-clerk-foreground-muted": colors.mutedForeground.toLowerCase(),
168 "--color-clerk-border": themeColorWithAlpha(colors.border, 0.06),
192 "--color-clerk-border": colors.border,
169193 "--color-clerk-danger": colors.danger.toLowerCase(),
170194 };
171195};
......@@ -181,6 +205,7 @@ export const renderUniwindThemesCSS = () => {
181205 const variants = [
182206 ...APPEARANCES.map((appearance) =>
183207 renderVariant(appearance, {
208 ...getMobileThemeVariables(DEFAULT_MOBILE_THEME_ID, appearance),
184209 ...adaptiveVariablesFor(appearance),
185210 ...clerkVariablesFor(appearance),
186211 }),
......@@ -202,50 +227,27 @@ export const renderUniwindThemesCSS = () => {
202227 ].join("\n");
203228};
204229
205const readVariantBody = (css: string, appearance: MobileThemeAppearance): string => {
206 const marker = `@variant ${appearance} {`;
207 const markerIndex = css.indexOf(marker);
208 if (markerIndex === -1) throw new Error(`Could not find ${marker} in global.css.`);
209
210 const openingBraceIndex = css.indexOf("{", markerIndex);
211 let depth = 0;
212 for (let index = openingBraceIndex; index < css.length; index += 1) {
213 if (css[index] === "{") depth += 1;
214 if (css[index] !== "}") continue;
215 depth -= 1;
216 if (depth === 0) return css.slice(openingBraceIndex + 1, index);
217 }
218 throw new Error(`Could not find the end of ${marker} in global.css.`);
219};
220
221export const readDefaultThemeVariables = (css: string) =>
222 Object.fromEntries(
223 APPEARANCES.map((appearance) => {
224 const body = readVariantBody(css, appearance);
225 const variables = Object.fromEntries(
226 MOBILE_THEME_VARIABLE_NAMES.map((name) => {
227 const match = new RegExp(`^\\s*${name}:\\s*([^;]+);`, "mu").exec(body);
228 if (!match?.[1]) {
229 throw new Error(`Default ${appearance} theme is missing ${name}.`);
230 }
231 return [name, match[1].trim()];
232 }),
233 ) as MobileThemeVariables;
234 return [appearance, variables];
235 }),
236 ) as Readonly<Record<MobileThemeAppearance, MobileThemeVariables>>;
237
238export const renderDefaultThemeVariablesJSON = (css: string) =>
239 `${JSON.stringify(readDefaultThemeVariables(css), null, 2)}\n`;
230export const renderDefaultThemeVariablesJSON = () =>
231 `${JSON.stringify(
232 Object.fromEntries(
233 APPEARANCES.map((appearance) => [
234 appearance,
235 getMobileThemeVariables(DEFAULT_MOBILE_THEME_ID, appearance),
236 ]),
237 ),
238 null,
239 2,
240 )}\n`;
240241
241242export const getGeneratedUniwindThemeOutputs = (): ReadonlyArray<
242243 readonly [filename: string, contents: string]
243244> => [
244245 [GENERATED_CSS_PATH, renderUniwindThemesCSS()],
245246 [GENERATED_NAMES_PATH, `${JSON.stringify(customThemeNames, null, 2)}\n`],
247 [GENERATED_DEFAULT_VARIABLES_PATH, renderDefaultThemeVariablesJSON()],
246248 [
247 GENERATED_DEFAULT_VARIABLES_PATH,
248 renderDefaultThemeVariablesJSON(NodeFS.readFileSync(GLOBAL_CSS_PATH, "utf8")),
249 GENERATED_CLERK_THEME_PATH,
250 `${JSON.stringify({ colors: clerkColorsFor("light"), darkColors: clerkColorsFor("dark"), design: { borderRadius: 18 } }, null, 2)}\n`,
249251 ],
250252];
251253
apps/mobile/scripts/generate-uniwind-themes.test.ts+14-10
......@@ -5,9 +5,10 @@ import { describe, expect, it } from "vite-plus/test";
55import {
66 customThemeNames,
77 getGeneratedUniwindThemeOutputs,
8 readDefaultThemeVariables,
8 renderDefaultThemeVariablesJSON,
99 renderUniwindThemesCSS,
1010} from "./generate-uniwind-themes.mts";
11import { readDefaultMobileThemeVariables } from "../src/lib/mobileTheme.test-support";
1112
1213describe("generate mobile Uniwind themes", () => {
1314 it("keeps the committed outputs current", () => {
......@@ -44,12 +45,15 @@ describe("generate mobile Uniwind themes", () => {
4445 }
4546 });
4647
47 it("generates the default runtime bridge from the authored CSS", () => {
48 const css = NodeFS.readFileSync(NodePath.resolve(import.meta.dirname, "../global.css"), "utf8");
49 const variables = readDefaultThemeVariables(css);
48 it("keeps the default runtime bridge and generated CSS on the same palette", () => {
49 const variables = JSON.parse(renderDefaultThemeVariablesJSON());
5050
51 expect(variables.light["--color-screen"]).toBe("#f2f2f7");
51 expect(variables.light).toEqual(readDefaultMobileThemeVariables("light"));
52 expect(variables.dark).toEqual(readDefaultMobileThemeVariables("dark"));
53 expect(variables.light["--color-screen"]).toBe("#fcfcfc");
54 expect(variables.light["--color-drawer"]).toBe("#fafafa");
5255 expect(variables.dark["--color-screen"]).toBe("#0a0a0a");
56 expect(variables.dark["--color-drawer"]).toBe("#000000");
5357 expect(Object.keys(variables.light)).toEqual(Object.keys(variables.dark));
5458 });
5559
......@@ -77,11 +81,11 @@ describe("generate mobile Uniwind themes", () => {
7781 ),
7882 name,
7983 ).toEqual({
80 "--color-clerk-page": isDark ? "#0e0e0e" : "#f2f2f7",
81 "--color-clerk-foreground": isDark ? "#f5f5f5" : "#262626",
82 "--color-clerk-foreground-muted": isDark ? "#a3a3a3" : "#737373",
83 "--color-clerk-border": isDark ? "rgba(42, 42, 42, 0.06)" : "rgba(229, 229, 234, 0.06)",
84 "--color-clerk-danger": isDark ? "#fca5a5" : "#dc2626",
84 "--color-clerk-page": isDark ? "#0a0a0a" : "#fcfcfc",
85 "--color-clerk-foreground": isDark ? "#f5f5f5" : "#27272a",
86 "--color-clerk-foreground-muted": isDark ? "#818181" : "#71717b",
87 "--color-clerk-border": isDark ? "#191919" : "#e4e4e7",
88 "--color-clerk-danger": isDark ? "#ff6467" : "#c10007",
8589 });
8690 }
8791 });
apps/mobile/src/components/AndroidScreenHeader.tsx+10-2
......@@ -25,7 +25,13 @@ export function AndroidHeaderIconButton(props: {
2525 readonly disabled?: boolean;
2626 readonly selected?: boolean;
2727}) {
28 return <MaterialIconButton {...props} variant={props.selected ? "tonal" : "standard"} />;
28 return (
29 <MaterialIconButton
30 {...props}
31 tintColorClassName="accent-header-foreground"
32 variant={props.selected ? "tonal" : "standard"}
33 />
34 );
2935}
3036
3137export function AndroidScreenHeader(props: {
......@@ -65,6 +71,7 @@ export function AndroidScreenHeader(props: {
6571 <MaterialIconButton
6672 accessibilityLabel="Navigate up"
6773 icon="arrow.left"
74 tintColorClassName="accent-header-foreground"
6875 onPress={props.onBack}
6976 />
7077 ) : null}
......@@ -72,7 +79,7 @@ export function AndroidScreenHeader(props: {
7279 {props.leading}
7380
7481 <View className={cn("min-w-0 flex-1", !props.onBack && "pl-1")}>
75 <Text numberOfLines={1} style={titleTypography} className="text-foreground">
82 <Text numberOfLines={1} style={titleTypography} className="text-header-foreground">
7683 {props.title}
7784 </Text>
7885 {props.subtitle ? (
......@@ -114,6 +121,7 @@ export function AndroidScreenHeader(props: {
114121 <MaterialIconButton
115122 accessibilityLabel="More actions"
116123 icon="ellipsis"
124 tintColorClassName="accent-header-foreground"
117125 onPress={open}
118126 />
119127 )}
apps/mobile/src/components/AppText.tsx+4-8
......@@ -18,7 +18,7 @@ export function AppText({ className, ...props }: AppTextProps) {
1818 return (
1919 <RNText
2020 className={cn("font-sans text-foreground", className)}
21 selectionColorClassName={Platform.OS === "android" ? "accent-primary/32" : undefined}
21 selectionColorClassName={Platform.OS === "android" ? "accent-focus/32" : undefined}
2222 {...props}
2323 />
2424 );
......@@ -42,13 +42,9 @@ export function AppTextInput({ className, ref, ...props }: AppTextInputProps) {
4242 className,
4343 )}
4444 placeholderTextColorClassName="accent-placeholder"
45 selectionColorClassName={
46 Platform.OS === "android" ? "accent-primary/32" : "accent-foreground-secondary"
47 }
48 cursorColorClassName={
49 Platform.OS === "android" ? "accent-primary" : "accent-foreground-secondary"
50 }
51 selectionHandleColorClassName={Platform.OS === "android" ? "accent-primary" : undefined}
45 selectionColorClassName={"accent-focus/32"}
46 cursorColorClassName={"accent-focus"}
47 selectionHandleColorClassName={Platform.OS === "android" ? "accent-focus" : undefined}
5248 {...props}
5349 />
5450 );
apps/mobile/src/components/ComposerContextSheet.tsx+3-5
......@@ -187,11 +187,9 @@ export function ComposerContextSheet(props: {
187187 onRequestClose={props.onClose}
188188 >
189189 <View
190 style={{
191 flex: 1,
192 justifyContent: "flex-end",
193 backgroundColor: Platform.OS === "android" ? "#00000066" : undefined,
194 }}
190 className={
191 Platform.OS === "android" ? "flex-1 justify-end bg-backdrop" : "flex-1 justify-end"
192 }
195193 >
196194 {Platform.OS === "android" ? (
197195 <Pressable
apps/mobile/src/components/ControlPill.tsx+3-1
......@@ -72,7 +72,9 @@ export function ControlPill(props: {
7272 ? props.disabled
7373 ? "text-foreground-muted"
7474 : "text-primary-foreground"
75 : "",
75 : variant === "danger"
76 ? "text-danger-foreground"
77 : "text-foreground",
7678 );
7779
7880 if (
apps/mobile/src/components/FilePreview.tsx+1-1
......@@ -46,7 +46,7 @@ function DocumentPreview(props: {
4646 }, [uri, name, props.source.mimeType, props.source.kind]);
4747 return (
4848 <Modal transparent animationType="fade" onRequestClose={props.onRequestClose}>
49 <View className="flex-1 items-center justify-center bg-black/40 p-6">
49 <View className="flex-1 items-center justify-center bg-backdrop p-6">
5050 <View className="w-full max-w-sm gap-4 rounded-2xl bg-sheet-solid p-6">
5151 <Text className="font-t3-semibold text-foreground">Opening document…</Text>
5252 <Text className="text-foreground-muted" numberOfLines={2}>
apps/mobile/src/components/GlassBackdrop.tsx+8-10
......@@ -6,12 +6,13 @@ import { themeColorWithAlpha } from "../lib/mobileTheme";
66
77/** Frosted backdrop for containers that clip their children to their shape. */
88export function GlassBackdrop(props: { readonly fallbackColor?: ColorValue }) {
9 const { themeAppearance } = useAppearancePreferences();
9 const { themeAppearance, themeVariables } = useAppearancePreferences();
1010 const supportsBlur = Platform.OS === "ios";
11 const colorStyle =
12 props.fallbackColor === undefined
13 ? undefined
14 : { backgroundColor: themeColorWithAlpha(String(props.fallbackColor), 1) };
11 const color = props.fallbackColor ?? themeVariables["--color-glass-fallback"];
12 const colorStyle = {
13 backgroundColor:
14 supportsBlur || typeof color !== "string" ? color : themeColorWithAlpha(color, 1),
15 };
1516
1617 return (
1718 <>
......@@ -25,11 +26,8 @@ export function GlassBackdrop(props: { readonly fallbackColor?: ColorValue }) {
2526 ) : null}
2627 <View
2728 pointerEvents="none"
28 className="absolute inset-0 bg-card"
29 style={[
30 colorStyle,
31 { opacity: supportsBlur ? (themeAppearance === "dark" ? 0.25 : 0.55) : 1 },
32 ]}
29 className="absolute inset-0 bg-glass-fallback"
30 style={colorStyle}
3331 />
3432 </>
3533 );
apps/mobile/src/components/GlassSurface.tsx+4-9
......@@ -1,16 +1,10 @@
11import { GlassView, isGlassEffectAPIAvailable } from "expo-glass-effect";
22import type { ReactNode, Ref } from "react";
3import {
4 Platform,
5 useColorScheme,
6 View,
7 type ColorValue,
8 type ViewProps,
9 type ViewStyle,
10} from "react-native";
3import { Platform, View, type ColorValue, type ViewProps, type ViewStyle } from "react-native";
114import { withUniwind } from "uniwind";
125
136import { cn } from "../lib/cn";
7import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider";
148import { GlassBackdrop } from "./GlassBackdrop";
159
1610// Explicit mappings keep the native glassEffectStyle enum out of style-array conversion.
......@@ -45,7 +39,8 @@ export function GlassSurface({
4539 style,
4640 ...props
4741}: GlassSurfaceProps) {
48 const isDarkMode = useColorScheme() === "dark";
42 const { themeAppearance } = useAppearancePreferences();
43 const isDarkMode = themeAppearance === "dark";
4944 const supportsGlass = Platform.OS === "ios" && isGlassEffectAPIAvailable();
5045 const hasShadow = chrome !== "none" && Platform.OS !== "android";
5146 const surfaceStyle: ViewStyle = {
apps/mobile/src/components/MaterialButton.android.tsx+1-1
......@@ -35,7 +35,7 @@ export function MaterialButton(props: MaterialButtonProps) {
3535 : tone === "danger"
3636 ? colors["--color-danger-foreground"]
3737 : tone === "text"
38 ? colors["--color-primary"]
38 ? colors["--color-primary-text"]
3939 : colors["--color-secondary-foreground"];
4040 return (
4141 <View
apps/mobile/src/components/MaterialButton.tsx+25-3
......@@ -1,5 +1,6 @@
11import { Pressable } from "react-native";
22import { AppText } from "./AppText";
3import { cn } from "../lib/cn";
34
45export interface MaterialButtonProps {
56 readonly label: string;
......@@ -10,15 +11,36 @@ export interface MaterialButtonProps {
1011 readonly fullWidth?: boolean;
1112}
1213
14const TONE_CLASS_NAMES = {
15 primary: ["bg-primary", "text-primary-foreground"],
16 secondary: ["bg-secondary", "text-secondary-foreground"],
17 danger: ["bg-danger", "text-danger-foreground"],
18 text: ["bg-transparent", "text-primary-text"],
19} as const;
20
1321export function MaterialButton(props: MaterialButtonProps) {
22 const [containerClassName, labelClassName] = TONE_CLASS_NAMES[props.tone ?? "secondary"];
23 const disabled = Boolean(props.disabled || props.loading);
1424 return (
1525 <Pressable
1626 accessibilityRole="button"
17 disabled={props.disabled}
27 accessibilityState={{ disabled }}
28 disabled={disabled}
1829 onPress={props.onPress}
19 className="min-h-12 justify-center rounded-full bg-secondary px-6"
30 className={cn(
31 "min-h-12 justify-center rounded-full px-6 active:opacity-70",
32 props.fullWidth ? "w-full" : "self-start",
33 disabled ? "bg-subtle-strong" : containerClassName,
34 )}
2035 >
21 <AppText>{props.label}</AppText>
36 <AppText
37 className={cn(
38 "text-center font-t3-medium",
39 disabled ? "text-foreground-muted" : labelClassName,
40 )}
41 >
42 {props.label}
43 </AppText>
2244 </Pressable>
2345 );
2446}
apps/mobile/src/components/MaterialConfirmDialog.android.tsx+9-4
......@@ -58,9 +58,9 @@ export function MaterialConfirmDialog(props: MaterialConfirmDialogProps) {
5858 colors={{
5959 focusedTextColor: colors["--color-foreground"],
6060 unfocusedTextColor: colors["--color-foreground"],
61 focusedIndicatorColor: colors["--color-primary"],
61 focusedIndicatorColor: colors["--color-focus"],
6262 unfocusedIndicatorColor: colors["--color-border"],
63 cursorColor: colors["--color-primary"],
63 cursorColor: colors["--color-focus"],
6464 }}
6565 >
6666 <OutlinedTextField.Label>
......@@ -74,7 +74,10 @@ export function MaterialConfirmDialog(props: MaterialConfirmDialogProps) {
7474 </AlertDialog.Text>
7575 ) : null}
7676 <AlertDialog.DismissButton>
77 <TextButton onClick={props.onCancel} colors={{ contentColor: colors["--color-primary"] }}>
77 <TextButton
78 onClick={props.onCancel}
79 colors={{ contentColor: colors["--color-primary-text"] }}
80 >
7881 <Text style={bodyTypography}>{props.request.cancelText ?? "Cancel"}</Text>
7982 </TextButton>
8083 </AlertDialog.DismissButton>
......@@ -84,7 +87,9 @@ export function MaterialConfirmDialog(props: MaterialConfirmDialogProps) {
8487 enabled={!props.confirmDisabled}
8588 colors={{
8689 contentColor:
87 colors[props.request.destructive ? "--color-danger" : "--color-primary"],
90 colors[
91 props.request.destructive ? "--color-danger-foreground" : "--color-primary-text"
92 ],
8893 }}
8994 >
9095 <Text style={bodyTypography}>{props.request.confirmText}</Text>
apps/mobile/src/components/MaterialFloatingActionButton.android.tsx+3-5
......@@ -25,9 +25,9 @@ export function MaterialFloatingActionButton(props: {
2525 const { themeAppearance, themeVariables: colors } = useAppearancePreferences();
2626 const typography = useScaledTextRole("footnote");
2727 const primary = props.tone === "primary";
28 const containerColor = colors[primary ? "--color-primary" : "--color-thread-selected"];
28 const containerColor = colors[primary ? "--color-primary" : "--color-secondary"];
2929 const contentColor =
30 colors[primary ? "--color-primary-foreground" : "--color-thread-selected-foreground"];
30 colors[primary ? "--color-primary-foreground" : "--color-secondary-foreground"];
3131 const Component =
3232 props.variant === "extended"
3333 ? ExtendedFloatingActionButton
......@@ -76,9 +76,7 @@ export function MaterialFloatingActionButton(props: {
7676 <SymbolView
7777 name={props.icon}
7878 size={iconSize}
79 tintColorClassName={
80 primary ? "accent-primary-foreground" : "accent-thread-selected-foreground"
81 }
79 tintColorClassName={primary ? "accent-primary-foreground" : "accent-secondary-foreground"}
8280 />
8381 </View>
8482 </View>
apps/mobile/src/components/MaterialFloatingActionButton.tsx+3-7
......@@ -15,7 +15,7 @@ export function MaterialFloatingActionButton(
1515 onPress={props.onPress}
1616 className={cn(
1717 "min-h-14 min-w-14 flex-row items-center justify-center gap-2 rounded-2xl px-4",
18 props.tone === "primary" ? "bg-primary" : "bg-thread-selected",
18 props.tone === "primary" ? "bg-primary" : "bg-secondary",
1919 props.className,
2020 )}
2121 style={props.style}
......@@ -24,18 +24,14 @@ export function MaterialFloatingActionButton(
2424 name={props.icon}
2525 size={24}
2626 tintColorClassName={
27 props.tone === "primary"
28 ? "accent-primary-foreground"
29 : "accent-thread-selected-foreground"
27 props.tone === "primary" ? "accent-primary-foreground" : "accent-secondary-foreground"
3028 }
3129 />
3230 {props.variant === "extended" && props.expanded !== false ? (
3331 <AppText
3432 className={cn(
3533 "text-sm",
36 props.tone === "primary"
37 ? "text-primary-foreground"
38 : "text-thread-selected-foreground",
34 props.tone === "primary" ? "text-primary-foreground" : "text-secondary-foreground",
3935 )}
4036 >
4137 {props.label}
apps/mobile/src/components/MaterialIconButton.android.tsx+2-1
......@@ -17,6 +17,7 @@ export function MaterialIconButton(props: {
1717 readonly disabled?: boolean;
1818 readonly selected?: boolean;
1919 readonly variant?: "standard" | "primary" | "tonal" | "danger";
20 readonly tintColorClassName?: string;
2021}) {
2122 const { themeAppearance, themeVariables: colors } = useAppearancePreferences();
2223 const variant = props.variant ?? "standard";
......@@ -40,7 +41,7 @@ export function MaterialIconButton(props: {
4041 ? "accent-danger-foreground"
4142 : variant === "tonal"
4243 ? "accent-secondary-foreground"
43 : "accent-foreground";
44 : (props.tintColorClassName ?? "accent-foreground");
4445 return (
4546 <View
4647 accessible
apps/mobile/src/components/MaterialIconButton.tsx+31-3
......@@ -1,7 +1,15 @@
1import { cn } from "../lib/cn";
12import { Pressable } from "react-native";
23
34import { SymbolView, type AppSymbolName } from "./AppSymbol";
45
6const VARIANT_CLASS_NAMES = {
7 standard: ["", "accent-foreground"],
8 primary: ["rounded-full bg-primary", "accent-primary-foreground"],
9 danger: ["rounded-full bg-danger", "accent-danger-foreground"],
10 tonal: ["rounded-full bg-secondary", "accent-secondary-foreground"],
11} as const;
12
513export function MaterialIconButton(props: {
614 readonly accessibilityLabel: string;
715 readonly icon: AppSymbolName;
......@@ -9,15 +17,35 @@ export function MaterialIconButton(props: {
917 readonly disabled?: boolean;
1018 readonly selected?: boolean;
1119 readonly variant?: "standard" | "primary" | "tonal" | "danger";
20 readonly tintColorClassName?: string;
1221}) {
22 const variant = props.variant ?? "standard";
23 const [containerClassName, iconTintClassName] = VARIANT_CLASS_NAMES[variant];
1324 return (
1425 <Pressable
15 {...props}
26 onPress={props.onPress}
27 disabled={props.disabled}
28 accessibilityLabel={props.accessibilityLabel}
1629 accessibilityRole="button"
1730 accessibilityState={{ disabled: Boolean(props.disabled), selected: props.selected }}
18 className="size-12 items-center justify-center"
31 className={cn(
32 "size-12 items-center justify-center",
33 props.disabled && variant !== "standard"
34 ? "rounded-full bg-subtle-strong"
35 : containerClassName,
36 )}
1937 >
20 <SymbolView name={props.icon} size={24} tintColorClassName="accent-foreground" />
38 <SymbolView
39 name={props.icon}
40 size={24}
41 tintColorClassName={
42 props.disabled
43 ? "accent-icon-subtle"
44 : variant === "standard"
45 ? (props.tintColorClassName ?? iconTintClassName)
46 : iconTintClassName
47 }
48 />
2149 </Pressable>
2250 );
2351}
apps/mobile/src/components/MaterialSearchField.tsx+3-3
......@@ -30,9 +30,9 @@ export function MaterialSearchField({
3030 returnKeyType="search"
3131 placeholder={placeholder}
3232 placeholderTextColorClassName="accent-placeholder"
33 selectionColorClassName="accent-primary/32"
34 cursorColorClassName="accent-primary"
35 selectionHandleColorClassName="accent-primary"
33 selectionColorClassName="accent-focus/32"
34 cursorColorClassName="accent-focus"
35 selectionHandleColorClassName="accent-focus"
3636 className="min-w-0 flex-1 py-2 font-sans text-base text-foreground"
3737 value={value}
3838 onChangeText={onChangeText}
apps/mobile/src/components/RowPressable.tsx+5-3
......@@ -9,22 +9,24 @@ import { useHoverGesture } from "../lib/useHoverGesture";
99export function RowPressable({
1010 children,
1111 className,
12 interactionClassName = "bg-primary",
12 interactionClassName = "bg-row-hover",
13 interactionOpacity = 1,
1314 ...props
1415}: Omit<ComponentProps<typeof Pressable>, "children"> & {
1516 readonly children: ReactNode;
1617 readonly interactionClassName?: string;
18 readonly interactionOpacity?: number;
1719}) {
1820 const { hovered, hoverGesture } = useHoverGesture(props.disabled ?? false);
1921 return (
2022 <GestureDetector gesture={hoverGesture}>
2123 <Pressable {...props} className={cn("relative overflow-hidden", className)}>
22 {({ pressed }) => (
24 {() => (
2325 <>
2426 <View
2527 pointerEvents="none"
2628 className={cn("absolute inset-0", interactionClassName)}
27 style={{ opacity: props.disabled || !hovered ? 0 : pressed ? 0.2 : 0.1 }}
29 style={{ opacity: props.disabled || !hovered ? 0 : interactionOpacity }}
2830 />
2931 {children}
3032 </>
apps/mobile/src/components/ScreenHeader.android.tsx+4-4
......@@ -53,7 +53,7 @@ export function ScreenHeader(props: ScreenHeaderProps) {
5353 <SymbolView
5454 name={menu.icon}
5555 size={16}
56 tintColorClassName="accent-icon"
56 tintColorClassName="accent-header-foreground"
5757 type="monochrome"
5858 />
5959 </Pressable>
......@@ -95,7 +95,7 @@ export function ScreenHeader(props: ScreenHeaderProps) {
9595 <SymbolView
9696 name="chevron.left"
9797 size={24}
98 tintColorClassName="accent-foreground"
98 tintColorClassName="accent-header-foreground"
9999 type="monochrome"
100100 />
101101 </Pressable>
......@@ -104,7 +104,7 @@ export function ScreenHeader(props: ScreenHeaderProps) {
104104 <SymbolView
105105 name="magnifyingglass"
106106 size={17}
107 tintColorClassName="accent-icon"
107 tintColorClassName="accent-header-foreground"
108108 type="monochrome"
109109 />
110110 <TextInput
......@@ -114,7 +114,7 @@ export function ScreenHeader(props: ScreenHeaderProps) {
114114 value={search.value}
115115 placeholder={search.placeholder}
116116 placeholderTextColorClassName="accent-placeholder"
117 className="flex-1 py-2 text-base font-sans text-foreground"
117 className="flex-1 py-2 text-base font-sans text-header-foreground"
118118 />
119119 </View>
120120 {menuView}
apps/mobile/src/components/SegmentedControl.tsx+2-2
......@@ -40,7 +40,7 @@ export function SegmentedControl<Value extends number | string>(
4040 layout={LinearTransition.duration(200)
4141 .easing(Easing.out(Easing.cubic))
4242 .reduceMotion(ReduceMotion.System)}
43 className="absolute inset-y-0 rounded-full bg-subtle-strong"
43 className="absolute inset-y-0 rounded-full bg-secondary"
4444 style={{
4545 width: `${100 / props.options.length}%`,
4646 start: `${
......@@ -70,7 +70,7 @@ export function SegmentedControl<Value extends number | string>(
7070 <Text
7171 className={cn(
7272 compact ? "text-xs" : "text-sm",
73 active ? "font-t3-medium text-foreground" : "text-foreground-muted",
73 active ? "font-t3-medium text-secondary-foreground" : "text-foreground-muted",
7474 )}
7575 >
7676 {option.label}
apps/mobile/src/features/connection/ConnectionStatusDot.tsx+17-10
......@@ -10,10 +10,16 @@ import Animated, {
1010} from "react-native-reanimated";
1111
1212import type { RemoteClientConnectionState } from "../../lib/connection";
13import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider";
14import { themeColorWithAlpha, type MobileThemeVariables } from "../../lib/mobileTheme";
1315
1416export type ConnectionStatusDotState = RemoteClientConnectionState;
1517
16function statusDotTone(state: ConnectionStatusDotState): {
18function statusDotTone(
19 state: ConnectionStatusDotState,
20 theme: MobileThemeVariables,
21 dark: boolean,
22): {
1723 readonly dotColor: string;
1824 readonly haloColor: string;
1925} {
......@@ -23,25 +29,25 @@ function statusDotTone(state: ConnectionStatusDotState): {
2329 case "available":
2430 case "unsupported":
2531 return {
26 dotColor: "#9ca3af",
27 haloColor: "rgba(156,163,175,0.42)",
32 dotColor: theme["--color-icon-muted"],
33 haloColor: themeColorWithAlpha(theme["--color-icon-muted"], 0.42),
2834 };
2935 case "connected":
3036 return {
31 dotColor: "#34d399",
32 haloColor: "rgba(52,211,153,0.48)",
37 dotColor: dark ? "#34d399" : "#059669",
38 haloColor: themeColorWithAlpha(dark ? "#34d399" : "#059669", 0.48),
3339 };
3440 case "connecting":
3541 case "reconnecting":
3642 return {
37 dotColor: "#f59e0b",
38 haloColor: "rgba(245,158,11,0.5)",
43 dotColor: theme["--color-warning-foreground"],
44 haloColor: themeColorWithAlpha(theme["--color-warning-foreground"], 0.5),
3945 };
4046 case "offline":
4147 case "error":
4248 return {
43 dotColor: "#ef4444",
44 haloColor: "rgba(239,68,68,0.48)",
49 dotColor: theme["--color-danger-foreground"],
50 haloColor: themeColorWithAlpha(theme["--color-danger-foreground"], 0.48),
4551 };
4652 }
4753}
......@@ -78,7 +84,8 @@ export function ConnectionStatusDot(props: {
7884 readonly size?: number;
7985}) {
8086 const pulseProgress = usePulseAnimation(props.pulse);
81 const tone = statusDotTone(props.state);
87 const { themeAppearance, themeVariables } = useAppearancePreferences();
88 const tone = statusDotTone(props.state, themeVariables, themeAppearance === "dark");
8289 const dotSize = props.size ?? 10;
8390 const haloSize = dotSize + 4;
8491 const containerSize = haloSize + 4;
apps/mobile/src/features/connection/connectionTone.ts+2-2
......@@ -18,8 +18,8 @@ export function connectionTone(state: RemoteClientConnectionState): StatusTone {
1818 case "connecting":
1919 return {
2020 label: "Connecting",
21 pillClassName: "bg-primary/10",
22 textClassName: "text-foreground-secondary",
21 pillClassName: "bg-update",
22 textClassName: "text-update-foreground",
2323 };
2424 case "unsupported":
2525 return {
apps/mobile/src/features/devices/DevicePreviewRouteScreen.tsx+7-5
......@@ -231,10 +231,10 @@ function DevicePreviewScreen({
231231 </AppText>
232232 <Pressable
233233 accessibilityRole="button"
234 className="rounded-full bg-subtle px-6 py-3"
234 className="rounded-full border border-secondary-border bg-secondary px-6 py-3"
235235 onPress={state.refresh}
236236 >
237 <AppText>Retry</AppText>
237 <AppText className="text-secondary-foreground">Retry</AppText>
238238 </Pressable>
239239 </>
240240 ) : focused && foreground ? (
......@@ -278,7 +278,9 @@ function OpenDevicePreview({
278278 background: themeVariables["--color-sheet-solid"],
279279 foreground: themeVariables["--color-foreground"],
280280 muted: themeVariables["--color-foreground-muted"],
281 buttonBackground: themeVariables["--color-subtle"],
281 buttonBackground: themeVariables["--color-secondary"],
282 buttonForeground: themeVariables["--color-secondary-foreground"],
283 buttonBorder: themeVariables["--color-secondary-border"],
282284 }}
283285 onUnauthorized={onUnauthorized}
284286 onInputConnected={onInputConnected}
......@@ -292,10 +294,10 @@ function OpenDevicePreview({
292294 </AppText>
293295 <Pressable
294296 accessibilityRole="button"
295 className="rounded-full bg-subtle px-6 py-3"
297 className="rounded-full border border-secondary-border bg-secondary px-6 py-3"
296298 onPress={refresh}
297299 >
298 <AppText>Retry</AppText>
300 <AppText className="text-secondary-foreground">Retry</AppText>
299301 </Pressable>
300302 </>
301303 ) : (
apps/mobile/src/features/devices/device-stream-document.ts+2
......@@ -10,6 +10,8 @@ export interface DeviceStreamConfiguration {
1010 readonly foreground: string;
1111 readonly muted: string;
1212 readonly buttonBackground: string;
13 readonly buttonForeground: string;
14 readonly buttonBorder: string;
1315 };
1416}
1517
apps/mobile/src/features/devices/device-stream.browser.ts+2-2
......@@ -90,9 +90,9 @@ export function start(configuration: DeviceStreamConfiguration) {
9090 Object.assign(retry.style, {
9191 padding: "12px 24px",
9292 borderRadius: "20px",
93 border: "0",
93 border: `1px solid ${colors.buttonBorder}`,
9494 background: colors.buttonBackground,
95 color: colors.foreground,
95 color: colors.buttonForeground,
9696 font: "inherit",
9797 display: "none",
9898 });
apps/mobile/src/features/files/SourceFileSurface.tsx+3-3
......@@ -74,7 +74,7 @@ const HighlightedSourceLine = memo(function HighlightedSourceLine(props: {
7474 </NativeText>
7575 <NativeText
7676 selectable
77 selectionColorClassName={Platform.OS === "android" ? "accent-primary/32" : undefined}
77 selectionColorClassName={Platform.OS === "android" ? "accent-focus/32" : undefined}
7878 numberOfLines={props.wordBreak ? undefined : 1}
7979 className="flex-1 font-normal text-foreground"
8080 style={{
......@@ -105,7 +105,7 @@ const HighlightedSourceLine = memo(function HighlightedSourceLine(props: {
105105 key={`${start}:${token.content.length}:${token.color ?? ""}`}
106106 selectable
107107 selectionColorClassName={
108 Platform.OS === "android" ? "accent-primary/32" : undefined
108 Platform.OS === "android" ? "accent-focus/32" : undefined
109109 }
110110 style={{
111111 color: token.color ?? undefined,
......@@ -279,7 +279,7 @@ function JavaScriptSourceFileSurface(props: SourceFileSurfaceProps) {
279279 <MarkdownTextPrimitive
280280 uiTextView
281281 selectable
282 selectionColorClassName={Platform.OS === "android" ? "accent-primary/32" : undefined}
282 selectionColorClassName={Platform.OS === "android" ? "accent-focus/32" : undefined}
283283 style={{
284284 color: foreground,
285285 fontFamily: REVIEW_MONO_FONT_FAMILY,
apps/mobile/src/features/keyboard/CommandPalette.tsx+30-8
......@@ -20,6 +20,7 @@ import { GlassSurface } from "../../components/GlassSurface";
2020import { RowPressable } from "../../components/RowPressable";
2121import { AppText as Text } from "../../components/AppText";
2222import { SymbolView, type AppSymbolName } from "../../components/AppSymbol";
23import { cn } from "../../lib/cn";
2324import { scopedProjectKey, scopedThreadKey } from "../../lib/scopedEntities";
2425import { T3KeyboardCommands } from "../../native/T3KeyboardCommands";
2526import { useProjects, useThreadShell, useThreadShells } from "../../state/entities";
......@@ -27,6 +28,7 @@ import { useThreadSearch } from "../../state/queries";
2728import { useWorkspaceState } from "../../state/workspace";
2829import { useSavedRemoteConnections } from "../../state/use-remote-environment-registry";
2930import { useAdaptiveWorkspaceLayout } from "../layout/AdaptiveWorkspaceLayout";
31import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider";
3032import { ThreadSearchMatchExcerpt } from "../threads/thread-search-match";
3133import {
3234 filterCommandPaletteItems,
......@@ -74,35 +76,53 @@ function PaletteRow(props: {
7476 readonly searchQuery: string;
7577 readonly onSelect: () => void;
7678}) {
79 const foregroundClassName = props.selected
80 ? "text-thread-selected-foreground"
81 : "text-foreground";
82 const mutedForegroundClassName = props.selected
83 ? "text-thread-selected-foreground-muted"
84 : "text-foreground-muted";
7785 return (
7886 <RowPressable
7987 accessibilityRole="button"
88 interactionOpacity={props.selected ? 0 : 1}
8089 accessibilityState={{ selected: props.selected }}
8190 onPress={props.onSelect}
8291 className={
8392 props.selected
84 ? "mx-2 flex-row items-center gap-3 rounded-xl bg-primary/10 px-3"
93 ? "mx-2 flex-row items-center gap-3 rounded-xl bg-thread-selected px-3"
8594 : "mx-2 flex-row items-center gap-3 rounded-xl px-3"
8695 }
8796 style={{ height: ROW_HEIGHT }}
8897 >
8998 <View className="w-7 items-center">
90 <SymbolView name={itemIcon(props.item)} size={20} tintColorClassName="accent-icon" />
99 <SymbolView
100 name={itemIcon(props.item)}
101 size={20}
102 tintColorClassName={props.selected ? "accent-thread-selected-foreground" : "accent-icon"}
103 />
91104 </View>
92105 <View className="flex-1">
93 <Text numberOfLines={1} className="text-base">
106 <Text numberOfLines={1} className={cn("text-base", foregroundClassName)}>
94107 {props.item.title}
95108 </Text>
96109 {props.searchMatch ? (
97 <ThreadSearchMatchExcerpt match={props.searchMatch} query={props.searchQuery} compact />
110 <ThreadSearchMatchExcerpt
111 match={props.searchMatch}
112 query={props.searchQuery}
113 selected={props.selected}
114 compact
115 />
98116 ) : props.item.detail ? (
99 <Text numberOfLines={1} className="text-sm text-foreground-muted">
117 <Text numberOfLines={1} className={cn("text-sm", mutedForegroundClassName)}>
100118 {props.item.detail}
101119 </Text>
102120 ) : null}
103121 </View>
104122 {props.index < 9 ? (
105 <NativeText className="w-8 shrink-0 text-right text-sm tabular-nums text-foreground-muted">
123 <NativeText
124 className={cn("w-8 shrink-0 text-right text-sm tabular-nums", mutedForegroundClassName)}
125 >
106126 ⌘{props.index + 1}
107127 </NativeText>
108128 ) : null}
......@@ -117,6 +137,7 @@ export function CommandPalette(props: {
117137 readonly onCommand: (command: HardwareKeyboardCommand) => void;
118138}) {
119139 const navigation = useNavigation();
140 const { themeVariables } = useAppearancePreferences();
120141 const { selectThread } = useAdaptiveWorkspaceLayout();
121142 const runCommand = props.onCommand;
122143 const projects = useProjects();
......@@ -392,14 +413,15 @@ export function CommandPalette(props: {
392413 className="flex-1 items-center justify-center p-4"
393414 >
394415 <Pressable
395 className="absolute inset-0 bg-black/15"
416 className="absolute inset-0 bg-backdrop"
396417 accessibilityLabel="Close command palette"
397418 onPress={() => close()}
398419 />
399420 <GlassSurface
400421 accessibilityViewIsModal
401 className="bg-sheet/70"
422 className="bg-sheet"
402423 tintColorClassName="accent-sheet/20"
424 fallbackColor={themeVariables["--color-sheet"]}
403425 style={{
404426 width: Math.min(600, width - 32),
405427 height: Math.min(520, height - 80),
apps/mobile/src/features/projects/AddProjectScreen.tsx+1-1
......@@ -129,7 +129,7 @@ function SectionTitle(props: { readonly children: string }) {
129129 <Text
130130 className={
131131 Platform.OS === "android"
132 ? "px-4 text-sm font-t3-medium text-primary"
132 ? "px-4 text-sm font-t3-medium text-primary-text"
133133 : "px-1 text-2xs font-t3-bold tracking-[0.7px] uppercase text-foreground-muted"
134134 }
135135 >
apps/mobile/src/features/review/ReviewSheet.tsx+5-3
......@@ -262,7 +262,7 @@ const ReviewFileNavigatorRow = memo(function ReviewFileNavigatorRow(props: {
262262 Platform.OS === "android"
263263 ? cn(
264264 "mt-1 min-h-12 justify-center rounded-[20px] px-3 py-2 active:bg-subtle",
265 selected && "bg-thread-selected",
265 selected && "bg-subtle-strong",
266266 )
267267 : selected
268268 ? "mt-1 min-h-12 justify-center rounded-xl bg-subtle-strong px-3 py-2"
......@@ -281,8 +281,10 @@ const ReviewFileNavigatorRow = memo(function ReviewFileNavigatorRow(props: {
281281 {file.path}
282282 </Text>
283283 <View className="mt-1 flex-row gap-2">
284 <Text className="text-2xs font-t3-bold text-emerald-600">+{file.additions}</Text>
285 <Text className="text-2xs font-t3-bold text-rose-600">-{file.deletions}</Text>
284 <Text className="text-2xs font-t3-bold text-adaptive-emerald-700-300">
285 +{file.additions}
286 </Text>
287 <Text className="text-2xs font-t3-bold text-adaptive-rose-700-300">-{file.deletions}</Text>
286288 </View>
287289 </Pressable>
288290 );
apps/mobile/src/features/review/nativeReviewDiffAdapter.test.ts+19-2
......@@ -97,6 +97,19 @@ function appTheme(themeId: MobileThemeId, appearance: MobileThemeAppearance) {
9797 : getMobileThemeVariables(themeId, appearance);
9898}
9999
100function contrastRatio(first: string, second: string): number {
101 const luminance = (hex: string) => {
102 const [red, green, blue] = [1, 3, 5].map((offset) => {
103 const channel = Number.parseInt(hex.slice(offset, offset + 2), 16) / 255;
104 return channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;
105 });
106 return 0.2126 * red! + 0.7152 * green! + 0.0722 * blue!;
107 };
108 const a = luminance(first);
109 const b = luminance(second);
110 return (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);
111}
112
100113describe("getCachedNativeReviewDiffData", () => {
101114 it.each([true, false])(
102115 "preserves available diff rows before a notice (has excerpt: %s)",
......@@ -251,6 +264,10 @@ describe("createNativeReviewDiffTheme", () => {
251264 for (const color of Object.values(theme)) {
252265 expect(color, `${themeId}/${appearance}`).toMatch(/^#[\da-f]{6}$/i);
253266 }
267 expect(
268 contrastRatio(theme.hunkText, theme.hunkBackground),
269 `${themeId}/${appearance} hunk text`,
270 ).toBeGreaterThanOrEqual(4.5);
254271 }
255272 }
256273 });
......@@ -261,7 +278,7 @@ describe("createNativeReviewDiffTheme", () => {
261278 const variables = {
262279 ...appTheme("material-you", appearance),
263280 "--color-screen": "#101214FF",
264 "--color-sheet": "#20222480",
281 "--color-md-code-bg": "#20222480",
265282 "--color-md-code-text": "#E3E2E6FF",
266283 "--color-foreground-muted": "#C7C5D080",
267284 "--color-border": "#44464F80",
......@@ -273,7 +290,7 @@ describe("createNativeReviewDiffTheme", () => {
273290 expect(theme.text).toBe("#e3e2e6");
274291 expect(theme.mutedText).toBe("#707076");
275292 expect(theme.border).toBe("#2e3036");
276 expect(theme.hunkText).toBe("#a8c7fa");
293 expect(contrastRatio(theme.hunkText, theme.hunkBackground)).toBeGreaterThanOrEqual(4.5);
277294 for (const color of Object.values(theme)) {
278295 expect(color).toMatch(/^#[\da-f]{6}$/i);
279296 }
apps/mobile/src/features/review/nativeReviewDiffAdapter.ts+4-7
......@@ -177,20 +177,19 @@ export function createNativeReviewDiffTheme(
177177 appTheme["--color-screen"],
178178 scheme === "dark" ? "#000000" : "#ffffff",
179179 );
180 const background = opaqueNativeHexColor(appTheme["--color-sheet"], screen);
180 const background = opaqueNativeHexColor(appTheme["--color-md-code-bg"], screen);
181181 const nativeColor = (color: string) => opaqueNativeHexColor(color, background);
182182
183183 if (scheme === "dark") {
184184 return {
185 // Match the app surface (--color-sheet) so code views blend with the rest of
186 // the app instead of using a distinct code-editor background.
185 // Code surfaces share the desktop palette rather than the sheet behind them.
187186 background,
188187 text: nativeColor(appTheme["--color-md-code-text"]),
189188 mutedText: nativeColor(appTheme["--color-foreground-muted"]),
190189 headerBackground: background,
191190 border: nativeColor(appTheme["--color-border"]),
192191 hunkBackground: nativeColor(appTheme["--color-subtle-strong"]),
193 hunkText: nativeColor(appTheme["--color-primary"]),
192 hunkText: nativeColor(appTheme["--color-foreground"]),
194193 addBackground: "#0d2f28",
195194 deleteBackground: "#391415",
196195 addBar: "#00cab1",
......@@ -201,15 +200,13 @@ export function createNativeReviewDiffTheme(
201200 }
202201
203202 return {
204 // Match the app surface (--color-sheet) so code views blend with the rest of the
205 // app instead of using a distinct code-editor background.
206203 background,
207204 text: nativeColor(appTheme["--color-md-code-text"]),
208205 mutedText: nativeColor(appTheme["--color-foreground-muted"]),
209206 headerBackground: background,
210207 border: nativeColor(appTheme["--color-border"]),
211208 hunkBackground: nativeColor(appTheme["--color-subtle-strong"]),
212 hunkText: nativeColor(appTheme["--color-primary"]),
209 hunkText: nativeColor(appTheme["--color-foreground"]),
213210 addBackground: "#e5f8f5",
214211 deleteBackground: "#ffe6e7",
215212 addBar: "#00cab1",
apps/mobile/src/features/settings/SettingsOpenSourceLicensesRouteScreen.tsx+4-4
......@@ -44,7 +44,7 @@ function LicenseRow(props: {
4444 accessibilityLabel={`${props.entry.name}, ${props.entry.license}`}
4545 accessibilityRole="button"
4646 onPress={props.onPress}
47 className="border-b border-border bg-card px-5 py-4 active:bg-card-alt"
47 className="border-b border-border bg-grouped-card px-5 py-4 active:bg-card-alt"
4848 >
4949 <View className="flex-row items-start gap-3">
5050 <View className="min-w-0 flex-1 gap-1">
......@@ -235,11 +235,11 @@ export function SettingsOpenSourceLicenseRouteScreen({ route }: LicenseDetailPro
235235 onPress={() => void Linking.openURL(sourceUrl)}
236236 className="min-h-12 flex-row items-center gap-2 self-start py-2 active:opacity-60"
237237 >
238 <Text className="font-t3-medium text-primary">Project source</Text>
238 <Text className="font-t3-medium text-primary-text">Project source</Text>
239239 <SymbolView
240240 name="arrow.up.right"
241241 size={16}
242 tintColorClassName={"accent-primary"}
242 tintColorClassName={"accent-primary-text"}
243243 type="monochrome"
244244 weight="semibold"
245245 />
......@@ -247,7 +247,7 @@ export function SettingsOpenSourceLicenseRouteScreen({ route }: LicenseDetailPro
247247 ) : null}
248248 </View>
249249
250 <View className="overflow-hidden rounded-[24px] border-continuous bg-card p-4">
250 <View className="overflow-hidden rounded-[24px] border-continuous bg-grouped-card p-4">
251251 <Text selectable className="font-mono text-base leading-normal text-foreground">
252252 {entry.noticeText}
253253 </Text>
apps/mobile/src/features/settings/appearance/sections/ThemeAppearanceSection.tsx+4-2
......@@ -151,7 +151,7 @@ function ThemeCard(props: {
151151 );
152152
153153 return (
154 <View className="min-w-36 flex-1 basis-[47%] gap-3 rounded-[24px] border border-border bg-card px-2 py-4">
154 <View className="min-w-36 flex-1 basis-[47%] gap-3 rounded-[24px] border border-border bg-grouped-card px-2 py-4">
155155 <Pressable
156156 accessibilityHint="Sets both light and dark appearances"
157157 accessibilityLabel={`${props.label} theme`}
......@@ -259,7 +259,9 @@ function ModeCard(props: {
259259 accessibilityState={{ checked: props.selected, disabled: props.disabled }}
260260 className={cn(
261261 "min-w-0 flex-1 gap-2 rounded-[24px] p-2 active:scale-[0.97]",
262 props.selected ? "border-2 border-primary bg-subtle" : "border border-border bg-card",
262 props.selected
263 ? "border-2 border-primary bg-subtle"
264 : "border border-border bg-grouped-card",
263265 )}
264266 disabled={props.disabled}
265267 onPress={props.onPress}
apps/mobile/src/features/settings/components/AutoSettleDaysField.ios.tsx+3-3
......@@ -44,7 +44,7 @@ export function AutoSettleDaysField(props: AutoSettleDaysFieldProps) {
4444 disabled(props.disabled),
4545 accessibilityLabel(`Days before auto-settle: ${props.value}`),
4646 frame({ minWidth: 64, minHeight: 44 }),
47 foregroundStyle(colors["--color-primary"]),
47 foregroundStyle(colors["--color-primary-text"]),
4848 font({ size: appearance.baseFontSize }),
4949 ]}
5050 >
......@@ -78,7 +78,7 @@ export function AutoSettleDaysField(props: AutoSettleDaysFieldProps) {
7878 <Button
7979 label="Cancel"
8080 onPress={() => setOpen(false)}
81 modifiers={[foregroundStyle(colors["--color-primary"])]}
81 modifiers={[foregroundStyle(colors["--color-primary-text"])]}
8282 />
8383 <Button
8484 label="Done"
......@@ -86,7 +86,7 @@ export function AutoSettleDaysField(props: AutoSettleDaysFieldProps) {
8686 setOpen(false);
8787 if (!props.disabled && draft !== props.value) props.onValueChange(draft);
8888 }}
89 modifiers={[foregroundStyle(colors["--color-primary"])]}
89 modifiers={[foregroundStyle(colors["--color-primary-text"])]}
9090 />
9191 </HStack>
9292 </VStack>
apps/mobile/src/features/settings/components/SettingsActionRow.tsx+1
......@@ -31,6 +31,7 @@ export function SettingsActionRow(props: {
3131 if (Platform.OS === "android") {
3232 return (
3333 <MaterialListRow
34 className="bg-grouped-card"
3435 title={props.label}
3536 titleClassName={textClassName}
3637 leading={icon}
apps/mobile/src/features/settings/components/SettingsProjectOverridesSection.tsx+1-1
......@@ -23,7 +23,7 @@ export function SettingsProjectOverridesSection(props: {
2323 onPress={props.onClear}
2424 className="px-2 py-2 active:opacity-70"
2525 >
26 <Text className="text-sm font-t3-medium text-primary">Use defaults</Text>
26 <Text className="text-sm font-t3-medium text-primary-text">Use defaults</Text>
2727 </Pressable>
2828 ) : null}
2929 </View>
apps/mobile/src/features/settings/components/SettingsRow.tsx+1
......@@ -24,6 +24,7 @@ export function SettingsRow(props: {
2424 if (Platform.OS === "android") {
2525 return (
2626 <MaterialListRow
27 className="bg-grouped-card"
2728 title={props.label}
2829 subtitle={props.valuePosition === "trailing" ? undefined : props.value}
2930 accessibilityLabel={[props.label, props.value].filter(Boolean).join(", ")}
apps/mobile/src/features/settings/components/SettingsSection.tsx+3-3
......@@ -15,7 +15,7 @@ export function SettingsSection(props: {
1515 <Text
1616 className={
1717 Platform.OS === "android"
18 ? "px-4 text-sm font-t3-medium text-primary"
18 ? "px-4 text-sm font-t3-medium text-primary-text"
1919 : "px-2 text-sm font-t3-medium text-foreground-muted"
2020 }
2121 >
......@@ -27,8 +27,8 @@ export function SettingsSection(props: {
2727 <View
2828 className={
2929 Platform.OS === "android"
30 ? "overflow-hidden rounded-[28px] bg-card"
31 : "overflow-hidden rounded-[24px] border-continuous bg-card"
30 ? "overflow-hidden rounded-[28px] bg-grouped-card"
31 : "overflow-hidden rounded-[24px] border-continuous bg-grouped-card"
3232 }
3333 >
3434 {props.children}
apps/mobile/src/features/terminal/terminalTheme.test.ts+10-10
......@@ -6,20 +6,20 @@ import { themeColorToNativeColor } from "../../lib/mobileTheme";
66import { buildGhosttyThemeConfig, getMobileTerminalTheme } from "./terminalTheme";
77
88describe("getMobileTerminalTheme", () => {
9 it("preserves the default light terminal palette", () => {
9 it("uses the shared default light terminal colors", () => {
1010 expect(getMobileTerminalTheme("t3-code", "light")).toMatchObject({
11 background: "#f2f2f7",
12 foreground: "#6C6C71",
13 cursorForeground: "#009fff",
14 cursorBackground: "#f2f2f7",
11 background: "#fcfcfc",
12 foreground: "#27272a",
13 cursorForeground: "#26384e",
14 cursorBackground: "#fcfcfc",
1515 });
1616 });
1717
18 it("preserves the default dark terminal palette", () => {
18 it("uses the shared default dark terminal colors", () => {
1919 expect(getMobileTerminalTheme("t3-code", "dark")).toMatchObject({
2020 background: "#0a0a0a",
21 foreground: "#adadb1",
22 cursorForeground: "#009fff",
21 foreground: "#f5f5f5",
22 cursorForeground: "#b4cbff",
2323 cursorBackground: "#0a0a0a",
2424 });
2525 });
......@@ -48,8 +48,8 @@ describe("buildGhosttyThemeConfig", () => {
4848 const config = buildGhosttyThemeConfig(getMobileTerminalTheme("t3-code", "dark"));
4949
5050 expect(config).toContain("background = #0a0a0a");
51 expect(config).toContain("foreground = #adadb1");
52 expect(config).toContain("cursor-color = #009fff");
51 expect(config).toContain("foreground = #f5f5f5");
52 expect(config).toContain("cursor-color = #b4cbff");
5353 expect(config).toContain("palette = 0=#141415");
5454 expect(config).toContain("palette = 15=#c6c6c8");
5555 expect(config.endsWith("\n")).toBe(true);
apps/mobile/src/features/terminal/terminalTheme.ts+6-13
......@@ -1,10 +1,5 @@
11import {
2 BUILT_IN_THEMES,
3 T3_CHAT_THEME,
4 getThemeColorsForAppearance,
5} from "@t3tools/shared/themePalettes";
6
7import {
2 getMobileThemeColors,
83 getMobileThemeVariables,
94 themeColorToNativeColor,
105 type MobileThemeId,
......@@ -44,12 +39,12 @@ type TerminalPalette = readonly [
4439
4540const PIERRE_LIGHT_THEME: TerminalTheme = {
4641 // Pierre terminal palette with the app's shared screen background.
47 background: "#f2f2f7",
42 background: "#fcfcfc",
4843 foreground: "#6C6C71",
4944 mutedForeground: "#8E8E95",
5045 border: "#eeeeef",
5146 cursorForeground: "#009fff",
52 cursorBackground: "#f2f2f7",
47 cursorBackground: "#fcfcfc",
5348 palette: [
5449 "#1F1F21",
5550 "#ff2e3f",
......@@ -107,11 +102,9 @@ export function getMobileTerminalTheme(
107102 scheme: TerminalAppearanceScheme,
108103): TerminalTheme {
109104 const base = getPierreTerminalTheme(scheme);
110 if (themeId === "t3-code" || themeId === "material-you") return base;
111
112 const theme = BUILT_IN_THEMES.find((candidate) => candidate.id === themeId) ?? T3_CHAT_THEME;
113 const palette = getThemeColorsForAppearance(theme, scheme) ?? theme.colors;
114 const colors = getMobileThemeVariables(themeId, scheme);
105 const paletteId = themeId === "material-you" ? "t3-code" : themeId;
106 const palette = getMobileThemeColors(paletteId, scheme);
107 const colors = getMobileThemeVariables(paletteId, scheme);
115108 const background = themeColorToNativeColor(palette.terminalBackground);
116109 return {
117110 ...base,
apps/mobile/src/features/threads/CustomSnoozeSheet.shared.tsx+1-1
......@@ -31,7 +31,7 @@ export function CustomSnoozeSheet(props: {
3131 return (
3232 <Modal visible transparent animationType="fade" onRequestClose={props.onClose}>
3333 <KeyboardAvoidingView
34 className="flex-1 items-center justify-center bg-black/40 px-6"
34 className="flex-1 items-center justify-center bg-backdrop px-6"
3535 behavior={Platform.OS === "ios" ? "padding" : undefined}
3636 >
3737 <ScrollView
apps/mobile/src/features/threads/NewTaskContextPickerScreens.tsx+4-4
......@@ -66,7 +66,7 @@ function SelectionRow(props: {
6666 }
6767 trailing={
6868 props.selected ? (
69 <SymbolView name="checkmark" size={20} tintColorClassName="accent-primary" />
69 <SymbolView name="checkmark" size={20} tintColorClassName="accent-focus" />
7070 ) : null
7171 }
7272 accessibilityRole="radio"
......@@ -482,9 +482,9 @@ export function NewTaskBranchPickerRouteScreen() {
482482 autoCorrect={false}
483483 accessibilityLabel="Find a branch"
484484 className="h-12 rounded-full border border-input-border bg-input px-4 font-sans text-base text-foreground"
485 selectionColorClassName="accent-primary/32"
486 cursorColorClassName="accent-primary"
487 selectionHandleColorClassName="accent-primary"
485 selectionColorClassName="accent-focus/32"
486 cursorColorClassName="accent-focus"
487 selectionHandleColorClassName="accent-focus"
488488 onChangeText={flow.setBranchQuery}
489489 placeholder="Find a branch"
490490 placeholderTextColorClassName="accent-placeholder"
apps/mobile/src/features/threads/ThreadArrangementSheet.tsx+1-1
......@@ -379,7 +379,7 @@ export function ThreadArrangementSheet(props: { onClose: () => void }) {
379379 onPress={props.onClose}
380380 className="min-h-11 justify-center px-3"
381381 >
382 <Text className="text-base text-primary">Done</Text>
382 <Text className="text-base text-primary-text">Done</Text>
383383 </Pressable>
384384 </View>
385385 <Text className="px-5 pb-3 text-sm text-foreground-muted">
apps/mobile/src/features/threads/ThreadComposer.tsx+2-6
......@@ -244,12 +244,8 @@ export function ComposerSurface(props: {
244244 >
245245 <AnimatedGlassSurface
246246 chrome="none"
247 fallbackColor={
248 Platform.OS === "android" ? colors["--color-composer-surface"] : colors["--color-card"]
249 }
250 fallbackClassName={
251 Platform.OS === "android" ? "border border-composer-border" : "border border-border"
252 }
247 fallbackColor={colors["--color-composer-surface"]}
248 fallbackClassName="border border-composer-border"
253249 glassEffectStyle="regular"
254250 // The composer is a passive material containing interactive controls.
255251 // Keep native glass out of the interactive content's layout path.
apps/mobile/src/features/threads/ThreadCreationFailedCard.tsx+4-4
......@@ -13,14 +13,14 @@ export function ThreadCreationFailedCard(props: {
1313 readonly onEditTask: () => void;
1414}) {
1515 return (
16 <View className="gap-2.5 rounded-[20px] border border-adaptive-neutral-200-white-a6 bg-adaptive-neutral-100-900 p-4">
17 <Text className="font-t3-bold text-2xs uppercase tracking-[1.1px] text-adaptive-rose-700-300">
16 <View className="gap-2.5 rounded-[20px] border border-border-subtle bg-card-alt p-4">
17 <Text className="font-t3-bold text-2xs uppercase tracking-[1.1px] text-danger-foreground">
1818 Could not start task
1919 </Text>
20 <Text className="font-sans text-sm leading-normal text-adaptive-neutral-600-400">
20 <Text className="font-sans text-sm leading-normal text-foreground-secondary">
2121 {props.reason}
2222 </Text>
23 <Text className="font-sans text-xs leading-normal text-adaptive-neutral-600-400">
23 <Text className="font-sans text-xs leading-normal text-foreground-secondary">
2424 Your prompt was kept in the project draft.
2525 </Text>
2626 <View className="flex-row">
apps/mobile/src/features/threads/ThreadFeed.tsx+9-9
......@@ -888,7 +888,7 @@ function MarkdownCodeBlock(props: {
888888 >
889889 <NativeText
890890 selectable
891 selectionColorClassName={Platform.OS === "android" ? "accent-primary/32" : undefined}
891 selectionColorClassName={Platform.OS === "android" ? "accent-focus/32" : undefined}
892892 className="font-mono"
893893 style={{
894894 color: props.textColor,
......@@ -1400,7 +1400,7 @@ function renderFeedEntry(
14001400 accessibilityState={{ expanded: entry.expanded }}
14011401 onPress={() => props.onToggleTurnFold(entry.turnId)}
14021402 hitSlop={4}
1403 className="mb-1 min-h-11 flex-row items-center gap-2 border-b border-adaptive-neutral-200-a80-white-a8 px-2"
1403 className="mb-1 min-h-11 flex-row items-center gap-2 border-b border-border-subtle px-2"
14041404 style={{
14051405 minHeight: Math.max(TURN_FOLD_HEIGHT - 3.5, props.workRowSizing.estimatedRowHeight),
14061406 }}
......@@ -1467,7 +1467,7 @@ function renderFeedEntry(
14671467 accessibilityLabel={label}
14681468 className="mb-3 flex-row items-center gap-3 px-1 py-1"
14691469 >
1470 <View className="h-px flex-1 bg-adaptive-neutral-200-a80-white-a8" />
1470 <View className="h-px flex-1 bg-subtle" />
14711471 <View className="shrink-0 flex-row items-center gap-1.5">
14721472 <SymbolView
14731473 name="arrow.down.right.and.arrow.up.left"
......@@ -1477,7 +1477,7 @@ function renderFeedEntry(
14771477 />
14781478 <Text className="font-t3-medium text-xs text-foreground-muted">{label}</Text>
14791479 </View>
1480 <View className="h-px flex-1 bg-adaptive-neutral-200-a80-white-a8" />
1480 <View className="h-px flex-1 bg-subtle" />
14811481 </View>
14821482 );
14831483 }
......@@ -1599,8 +1599,8 @@ function renderFeedEntry(
15991599 mimeType={attachment.mimeType}
16001600 className={
16011601 inlineAttachmentIds.size
1602 ? "h-24 w-24 rounded-[14px] bg-white/15"
1603 : "aspect-[1.3] w-full rounded-[14px] bg-white/15"
1602 ? "h-24 w-24 rounded-[14px] bg-user-bubble-foreground/15"
1603 : "aspect-[1.3] w-full rounded-[14px] bg-user-bubble-foreground/15"
16041604 }
16051605 onPressPreview={props.onPressPreview}
16061606 />
......@@ -1636,7 +1636,7 @@ function renderFeedEntry(
16361636 ) : null}
16371637 </View>
16381638 <View className="mt-1 flex-row items-center justify-end gap-1 pr-0.5">
1639 <Text className="font-t3-medium text-xs tabular-nums text-adaptive-neutral-600-400">
1639 <Text className="font-t3-medium text-xs tabular-nums text-foreground-secondary">
16401640 {entry.pendingMessage && !entry.acknowledged ? "Pending" : timestampLabel}
16411641 </Text>
16421642 {entry.pendingMessage &&
......@@ -1715,7 +1715,7 @@ function renderFeedEntry(
17151715 attachmentId={attachment.id}
17161716 name={attachment.name}
17171717 mimeType={attachment.mimeType}
1718 className="mt-1.5 aspect-[1.3] w-full rounded-[18px] bg-adaptive-neutral-200-800"
1718 className="mt-1.5 aspect-[1.3] w-full rounded-[18px] bg-subtle-strong"
17191719 onPressPreview={props.onPressPreview}
17201720 />
17211721 ) : isFileAttachment(attachment) ? (
......@@ -1739,7 +1739,7 @@ function renderFeedEntry(
17391739 buttonSize={28}
17401740 iconSize={13}
17411741 />
1742 <Text className="font-t3-medium text-xs tabular-nums text-adaptive-neutral-600-400">
1742 <Text className="font-t3-medium text-xs tabular-nums text-foreground-secondary">
17431743 {timestampLabel}
17441744 </Text>
17451745 </View>
apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx+17-13
......@@ -152,7 +152,7 @@ function ThreadNavigationSidebarPane(
152152 props: ThreadNavigationSidebarProps & { readonly nativeChrome: boolean },
153153) {
154154 const { themeVariables: materialTheme } = useAppearancePreferences();
155 const screenColor = materialTheme["--color-screen"];
155 const drawerColor = materialTheme["--color-drawer"];
156156
157157 const insets = useSafeAreaInsets();
158158 const projects = useProjects();
......@@ -935,7 +935,7 @@ function ThreadNavigationSidebarPane(
935935 }),
936936 )}
937937 searchQuery={props.searchQuery}
938 pane={Platform.OS === "android" ? "screen" : "sidebar"}
938 pane="sidebar"
939939 selected={
940940 scopedThreadKey(thread.environmentId, thread.id) === props.selectedThreadKey
941941 }
......@@ -976,7 +976,7 @@ function ThreadNavigationSidebarPane(
976976 disabled={!shelfPreferencesLoaded}
977977 expanded={item.expanded}
978978 onToggle={toggleSnoozedShelf}
979 pane={Platform.OS === "android" ? "screen" : "sidebar"}
979 pane="sidebar"
980980 />
981981 );
982982 case "v2-settled-shelf":
......@@ -986,17 +986,21 @@ function ThreadNavigationSidebarPane(
986986 disabled={!shelfPreferencesLoaded}
987987 expanded={item.expanded}
988988 onToggle={toggleSettledShelf}
989 pane={Platform.OS === "android" ? "screen" : "sidebar"}
989 pane="sidebar"
990990 />
991991 );
992992 case "v2-show-more":
993993 return (
994 <ThreadListV2ShowMoreRow hiddenCount={item.hiddenCount} onPress={showMoreSettled} />
994 <ThreadListV2ShowMoreRow
995 pane="sidebar"
996 hiddenCount={item.hiddenCount}
997 onPress={showMoreSettled}
998 />
995999 );
9961000 case "header":
9971001 return (
9981002 <ThreadListGroupHeader
999 variant={Platform.OS === "android" ? "compact" : "sidebar"}
1003 variant="sidebar"
10001004 collapsed={item.collapsed}
10011005 isFirst={item.isFirst}
10021006 groupKey={item.group.key}
......@@ -1014,7 +1018,7 @@ function ThreadNavigationSidebarPane(
10141018 case "pending-task":
10151019 return (
10161020 <PendingTaskListRow
1017 variant={Platform.OS === "android" ? "compact" : "sidebar"}
1021 variant="sidebar"
10181022 pendingTask={item.pendingTask}
10191023 environmentLabel={
10201024 savedConnectionsById[item.pendingTask.environmentId]?.environmentLabel ?? null
......@@ -1064,7 +1068,7 @@ function ThreadNavigationSidebarPane(
10641068 case "show-more":
10651069 return (
10661070 <ThreadListShowMoreRow
1067 variant={Platform.OS === "android" ? "compact" : "sidebar"}
1071 variant="sidebar"
10681072 hiddenCount={item.hiddenCount}
10691073 canShowLess={item.canShowLess}
10701074 groupKey={item.groupKey}
......@@ -1168,8 +1172,8 @@ function ThreadNavigationSidebarPane(
11681172 <Text
11691173 className={
11701174 Platform.OS === "android"
1171 ? "px-4 py-4 text-center text-sm text-foreground-muted"
1172 : "px-2 py-4 text-sm text-foreground-muted"
1175 ? "px-4 py-4 text-center text-sm text-drawer-foreground-muted"
1176 : "px-2 py-4 text-sm text-drawer-foreground-muted"
11731177 }
11741178 >
11751179 {catalogState.isLoadingConnections
......@@ -1277,7 +1281,7 @@ function ThreadNavigationSidebarPane(
12771281 marginTop: stickyHeaderHeight,
12781282 marginHorizontal: 4,
12791283 paddingBottom: insets.bottom,
1280 backgroundColor: screenColor,
1284 backgroundColor: drawerColor,
12811285 borderTopLeftRadius: 28,
12821286 borderTopRightRadius: 28,
12831287 overflow: "hidden",
......@@ -1374,7 +1378,7 @@ function ThreadNavigationSidebarPane(
13741378 <SymbolView
13751379 name="magnifyingglass"
13761380 size={15}
1377 tintColorClassName="accent-foreground-muted"
1381 tintColorClassName="accent-drawer-foreground-muted"
13781382 type="monochrome"
13791383 />
13801384 <TextInput
......@@ -1390,7 +1394,7 @@ function ThreadNavigationSidebarPane(
13901394 cursorColorClassName={undefined}
13911395 selectionHandleColorClassName={undefined}
13921396 returnKeyType="search"
1393 className="h-[34px] flex-1 px-0 py-0 font-sans text-base text-foreground"
1397 className="h-[34px] flex-1 px-0 py-0 font-sans text-base text-drawer-foreground"
13941398 value={props.searchQuery}
13951399 />
13961400 </View>
apps/mobile/src/features/threads/ThreadSettingsRows.android.tsx+1-1
......@@ -12,7 +12,7 @@ export function ModelRow(props: ModelRowProps) {
1212 {...props}
1313 labelNumberOfLines={2}
1414 minimumHeight={56}
15 selectedClassName={props.selected ? "bg-thread-selected" : undefined}
15 selectedClassName={props.selected ? "bg-secondary" : undefined}
1616 leadingSelection={<MaterialRadioIndicator selected={props.selected} />}
1717 />
1818 );
apps/mobile/src/features/threads/ThreadSettingsSheet.tsx+3-3
......@@ -896,9 +896,9 @@ function ThreadSettingsMainContent(props: {
896896 onChangeText={session.setSearchQuery}
897897 placeholder="Find a model"
898898 placeholderTextColorClassName="accent-placeholder"
899 selectionColorClassName="accent-primary/32"
900 cursorColorClassName="accent-primary"
901 selectionHandleColorClassName="accent-primary"
899 selectionColorClassName="accent-focus/32"
900 cursorColorClassName="accent-focus"
901 selectionHandleColorClassName="accent-focus"
902902 value={session.searchQuery}
903903 />
904904 {session.searchQuery.length > 0 ? (
apps/mobile/src/features/threads/floating-working-control.tsx+3-3
......@@ -213,7 +213,7 @@ export function FloatingWorkingControl(props: {
213213 <View pointerEvents="box-none" className="flex-row items-center gap-4">
214214 <Animated.View
215215 pointerEvents={capsuleInteractive ? "box-none" : "none"}
216 className="h-11 items-center justify-center overflow-hidden rounded-full border border-border bg-card shadow-md shadow-black/10"
216 className="h-11 items-center justify-center overflow-hidden rounded-full border border-border bg-glass-fallback shadow-md shadow-black/10"
217217 style={capsuleStyle}
218218 >
219219 {capsuleContent}
......@@ -228,7 +228,7 @@ export function FloatingWorkingControl(props: {
228228 <ControlPill
229229 accessibilityLabel="Scroll to end"
230230 activateOnPressIn
231 className="h-11 w-11 border border-border bg-card shadow-md shadow-black/10"
231 className="h-11 w-11 border border-border bg-glass-fallback shadow-md shadow-black/10"
232232 disabled={!props.showScrollToEnd}
233233 icon={{ ios: "chevron.down", android: "keyboard_arrow_down" }}
234234 onPress={props.onScrollToEnd}
......@@ -248,7 +248,7 @@ export function FloatingWorkingControl(props: {
248248 <ControlPill
249249 accessibilityLabel="Scroll to end"
250250 activateOnPressIn
251 className="h-11 w-11 border border-border bg-card shadow-md shadow-black/10"
251 className="h-11 w-11 border border-border bg-glass-fallback shadow-md shadow-black/10"
252252 icon={{ ios: "chevron.down", android: "keyboard_arrow_down" }}
253253 onPress={props.onScrollToEnd}
254254 />
apps/mobile/src/features/threads/git/GitCommitSheet.tsx+8-6
......@@ -164,7 +164,7 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {
164164 <Text
165165 className={
166166 Platform.OS === "android"
167 ? "text-primary text-sm font-t3-medium"
167 ? "text-primary-text text-sm font-t3-medium"
168168 : "text-foreground text-2xs font-t3-bold uppercase"
169169 }
170170 >
......@@ -183,7 +183,7 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {
183183 <Text
184184 className={
185185 Platform.OS === "android"
186 ? "text-primary text-sm font-t3-medium"
186 ? "text-primary-text text-sm font-t3-medium"
187187 : "text-foreground text-2xs font-t3-bold uppercase"
188188 }
189189 >
......@@ -204,10 +204,12 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {
204204 <Text className="text-foreground flex-1 text-sm font-medium" numberOfLines={1}>
205205 {file.path}
206206 </Text>
207 <Text className="text-xs font-t3-bold text-emerald-500">
207 <Text className="text-xs font-t3-bold text-adaptive-emerald-700-300">
208208 +{file.insertions}
209209 </Text>
210 <Text className="text-xs font-t3-bold text-rose-500">-{file.deletions}</Text>
210 <Text className="text-xs font-t3-bold text-adaptive-rose-700-300">
211 -{file.deletions}
212 </Text>
211213 </View>
212214 ))}
213215 {selectedFiles.length > selectedFilePreview.length ? (
......@@ -286,10 +288,10 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {
286288 ) : null}
287289 </View>
288290 <View className="items-end gap-1">
289 <Text className="text-xs font-t3-bold text-emerald-500">
291 <Text className="text-xs font-t3-bold text-adaptive-emerald-700-300">
290292 +{file.insertions}
291293 </Text>
292 <Text className="text-xs font-t3-bold text-rose-500">
294 <Text className="text-xs font-t3-bold text-adaptive-rose-700-300">
293295 -{file.deletions}
294296 </Text>
295297 </View>
apps/mobile/src/features/threads/sidebar-navigation-shell.tsx+1-1
......@@ -52,7 +52,7 @@ const SidebarStack = createNativeStackNavigator();
5252 * navigation hooks used for header configuration inside the pane.
5353 */
5454export function SidebarNavigationShell(props: { readonly children: ReactNode }) {
55 const navigationTheme = useMobileNavigationTheme();
55 const navigationTheme = useMobileNavigationTheme("sidebar");
5656
5757 return (
5858 <NavigationIndependentTree>
apps/mobile/src/features/threads/thread-list-items.tsx+76-95
......@@ -157,7 +157,7 @@ export const ThreadListGroupHeader = memo(function ThreadListGroupHeader(props:
157157 className={
158158 compact
159159 ? "flex-shrink text-base font-t3-bold tracking-[0.2px] text-foreground-muted"
160 : "flex-shrink text-sm font-t3-bold tracking-[0.2px] text-foreground-muted"
160 : "flex-shrink text-sm font-t3-bold tracking-[0.2px] text-drawer-foreground-muted"
161161 }
162162 numberOfLines={1}
163163 >
......@@ -167,7 +167,7 @@ export const ThreadListGroupHeader = memo(function ThreadListGroupHeader(props:
167167 className={
168168 compact
169169 ? "flex-1 text-sm font-t3-medium text-foreground-tertiary"
170 : "flex-1 text-xs font-t3-medium text-foreground-tertiary"
170 : "flex-1 text-xs font-t3-medium text-drawer-foreground-muted"
171171 }
172172 >
173173 {props.threadCount}
......@@ -241,7 +241,7 @@ export const ThreadListShowMoreRow = memo(function ThreadListShowMoreRow(props:
241241 className={
242242 compact
243243 ? "text-sm font-t3-medium text-foreground-muted"
244 : "text-xs font-t3-medium text-foreground-muted"
244 : "text-xs font-t3-medium text-drawer-foreground-muted"
245245 }
246246 >
247247 {label}
......@@ -320,7 +320,14 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {
320320 </View>
321321 ) : (
322322 <View className="rounded-full bg-subtle px-1.5 py-0.5">
323 <Text className="text-3xs font-t3-bold text-foreground-muted">Pending</Text>
323 <Text
324 className={cn(
325 "text-3xs font-t3-bold text-foreground-muted",
326 !compact && "text-drawer-foreground-muted",
327 )}
328 >
329 Pending
330 </Text>
324331 </View>
325332 );
326333
......@@ -330,21 +337,21 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {
330337 <SymbolView
331338 name={isDraft ? "square.and.pencil" : "tray.and.arrow.up"}
332339 size={10}
333 tintColorClassName={compact ? "accent-icon-subtle" : "accent-foreground-muted"}
340 tintColorClassName={compact ? "accent-icon-subtle" : "accent-drawer-foreground-muted"}
334341 type="monochrome"
335342 />
336343 {props.environmentLabel && props.environmentMachine ? (
337344 <EnvironmentMachineSymbol
338345 kind={props.environmentMachine}
339346 size={compact ? 12 : 10}
340 tintColorClassName={compact ? "accent-icon-subtle" : "accent-foreground-muted"}
347 tintColorClassName={compact ? "accent-icon-subtle" : "accent-drawer-foreground-muted"}
341348 />
342349 ) : null}
343350 <Text
344351 className={
345352 compact
346353 ? "shrink text-sm text-foreground-muted"
347 : "shrink text-xs text-foreground-muted"
354 : "shrink text-xs text-drawer-foreground-muted"
348355 }
349356 numberOfLines={1}
350357 >
......@@ -369,13 +376,26 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {
369376 <View className="pr-[18px] pt-[10px]" style={{ paddingLeft: THREAD_LIST_COMPACT_INSET }}>
370377 <View className={cn("gap-[3px] pb-[10px]", !props.isLast && "border-b border-separator")}>
371378 <View className="flex-row items-center justify-between gap-2">
372 <Text className="flex-1 text-lg font-t3-bold text-foreground" numberOfLines={1}>
379 <Text
380 className={cn(
381 "flex-1 text-lg font-t3-bold text-foreground",
382 !compact && "text-drawer-foreground",
383 )}
384 numberOfLines={1}
385 >
373386 {pendingTask.title}
374387 </Text>
375388 <View className="flex-row items-center gap-2">
376389 {statusPill}
377390 {timestamp !== null ? (
378 <Text className="text-base tabular-nums text-foreground-tertiary">{timestamp}</Text>
391 <Text
392 className={cn(
393 "text-base tabular-nums text-foreground-tertiary",
394 !compact && "text-drawer-foreground-muted",
395 )}
396 >
397 {timestamp}
398 </Text>
379399 ) : null}
380400 <SymbolView
381401 name="chevron.right"
......@@ -395,6 +415,7 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {
395415 accessibilityHint={accessibilityHint}
396416 accessibilityLabel={pendingTask.title}
397417 accessibilityRole="button"
418 interactionClassName="bg-thread-hover"
398419 onPress={() => onSelectPendingTask(pendingTask)}
399420 style={{
400421 borderRadius: SIDEBAR_ROW_RADIUS,
......@@ -406,13 +427,22 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {
406427 >
407428 <View className="gap-[3px]">
408429 <View className="flex-row items-center justify-between gap-2">
409 <Text className="flex-1 text-base font-t3-medium text-foreground" numberOfLines={1}>
430 <Text
431 className="flex-1 text-base font-t3-medium text-drawer-foreground"
432 numberOfLines={1}
433 >
410434 {pendingTask.title}
411435 </Text>
412436 <View className="flex-row items-center gap-2">
413437 {statusPill}
414438 {timestamp !== null ? (
415 <Text className="text-xs tabular-nums text-foreground-muted" numberOfLines={1}>
439 <Text
440 className={cn(
441 "text-xs tabular-nums text-foreground-muted",
442 !compact && "text-drawer-foreground-muted",
443 )}
444 numberOfLines={1}
445 >
416446 {timestamp}
417447 </Text>
418448 ) : null}
......@@ -477,10 +507,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
477507 const theme = useUniwindTheme();
478508 const screenColor = theme["--color-screen"];
479509 const drawerColor = theme["--color-drawer"];
480 const selectedBackgroundColor = theme["--color-user-bubble"];
481 const materialSelectedBackgroundColor = theme["--color-thread-selected"];
482 const materialSelectedForegroundColor = theme["--color-thread-selected-foreground"];
483 const selectedForegroundColor = theme["--color-user-bubble-foreground"];
510 const selectedBackgroundColor = theme["--color-thread-selected"];
484511
485512 const {
486513 thread,
......@@ -508,24 +535,6 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
508535 );
509536
510537 const backgroundColor = compact ? screenColor : drawerColor;
511 const effectiveSelectedBackground =
512 Platform.OS === "android" ? materialSelectedBackgroundColor : selectedBackgroundColor;
513 const effectiveSelectedForeground =
514 Platform.OS === "android" ? materialSelectedForegroundColor : selectedForegroundColor;
515 const effectiveStatus =
516 visuallySelected && status
517 ? {
518 ...status,
519 pillClassName:
520 Platform.OS === "android"
521 ? "bg-thread-selected-foreground/20"
522 : "bg-user-bubble-foreground/20",
523 textClassName:
524 Platform.OS === "android"
525 ? "text-thread-selected-foreground"
526 : "text-user-bubble-foreground",
527 }
528 : status;
529538
530539 const handleDelete = useCallback(() => onDeleteThread(thread), [onDeleteThread, thread]);
531540 const handleArchive = useCallback(() => onArchiveThread(thread), [onArchiveThread, thread]);
......@@ -580,11 +589,9 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
580589 [handleArchive, handleDelete, handleRegenerateTitle, handleRename, onNewThreadOnBranch, thread],
581590 );
582591
583 const statusPill = effectiveStatus ? (
584 <View className={`${effectiveStatus.pillClassName} rounded-full px-1.5 py-0.5`}>
585 <Text className={`text-3xs font-t3-bold ${effectiveStatus.textClassName}`}>
586 {effectiveStatus.label}
587 </Text>
592 const statusPill = status ? (
593 <View className={`${status.pillClassName} rounded-full px-1.5 py-0.5`}>
594 <Text className={`text-3xs font-t3-bold ${status.textClassName}`}>{status.label}</Text>
588595 </View>
589596 ) : null;
590597
......@@ -601,10 +608,8 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
601608 compact
602609 ? "accent-icon-subtle"
603610 : selected
604 ? Platform.OS === "android"
605 ? "accent-thread-selected-foreground-muted"
606 : "accent-user-bubble-foreground-muted"
607 : "accent-foreground-muted"
611 ? "accent-thread-selected-foreground-muted"
612 : "accent-drawer-foreground-muted"
608613 }
609614 />
610615 ) : null}
......@@ -613,10 +618,10 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
613618 "shrink",
614619 compact ? "text-sm" : "text-xs",
615620 visuallySelected
616 ? Platform.OS === "android"
617 ? "text-thread-selected-foreground-muted"
618 : "text-user-bubble-foreground-muted"
619 : "text-foreground-muted",
621 ? "text-thread-selected-foreground-muted"
622 : compact
623 ? "text-foreground-muted"
624 : "text-drawer-foreground-muted",
620625 )}
621626 numberOfLines={1}
622627 >
......@@ -633,30 +638,16 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
633638 <SymbolView
634639 name="square.3.layers.3d"
635640 size={compact ? 13 : 11}
636 tintColor={
637 visuallySelected
638 ? String(effectiveSelectedForeground)
639 : pullRequestTintColor(pr, colorScheme)
640 }
641 tintColor={pullRequestTintColor(pr, colorScheme)}
641642 />
642643 ) : (
643644 <PullRequestIcon
644645 size={compact ? 13 : 11}
645 color={
646 visuallySelected
647 ? String(effectiveSelectedForeground)
648 : pullRequestTintColor(pr, colorScheme)
649 }
646 color={pullRequestTintColor(pr, colorScheme)}
650647 />
651648 )}
652649 <Text
653 className={`${compact ? "text-sm" : "text-xs"} font-t3-medium ${
654 visuallySelected
655 ? Platform.OS === "android"
656 ? "text-thread-selected-foreground"
657 : "text-user-bubble-foreground"
658 : pr.textClassName
659 }`}
650 className={`${compact ? "text-sm" : "text-xs"} font-t3-medium ${pr.textClassName}`}
660651 >
661652 {pr.label}
662653 </Text>
......@@ -669,13 +660,8 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
669660 compact ? (
670661 <RowPressable
671662 key={`${thread.environmentId}:${thread.id}`}
672 interactionClassName={
673 visuallySelected
674 ? Platform.OS === "android"
675 ? "bg-thread-selected-foreground"
676 : "bg-user-bubble-foreground"
677 : "bg-primary"
678 }
663 interactionClassName="bg-row-hover"
664 interactionOpacity={visuallySelected ? 0 : 1}
679665 accessibilityHint="Swipe left for archive and delete actions"
680666 accessibilityLabel={threadAccessibilityLabel}
681667 accessibilityRole="button"
......@@ -683,7 +669,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
683669 style={
684670 Platform.OS === "android"
685671 ? {
686 backgroundColor: visuallySelected ? effectiveSelectedBackground : backgroundColor,
672 backgroundColor: visuallySelected ? selectedBackgroundColor : backgroundColor,
687673 borderRadius: 20,
688674 }
689675 : undefined
......@@ -705,10 +691,10 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
705691 className={cn(
706692 "flex-1 text-lg font-t3-bold",
707693 visuallySelected
708 ? Platform.OS === "android"
709 ? "text-thread-selected-foreground"
710 : "text-user-bubble-foreground"
711 : "text-foreground",
694 ? "text-thread-selected-foreground"
695 : compact
696 ? "text-foreground"
697 : "text-drawer-foreground",
712698 )}
713699 numberOfLines={1}
714700 >
......@@ -723,10 +709,8 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
723709 className={cn(
724710 "text-base tabular-nums",
725711 visuallySelected
726 ? Platform.OS === "android"
727 ? "text-thread-selected-foreground-muted"
728 : "text-user-bubble-foreground-muted"
729 : "text-foreground-tertiary",
712 ? "text-thread-selected-foreground-muted"
713 : "text-drawer-foreground-muted",
730714 )}
731715 >
732716 {timestamp}
......@@ -742,6 +726,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
742726 {props.searchMatch ? (
743727 <ThreadSearchMatchExcerpt
744728 compact
729 sidebar={!compact}
745730 match={props.searchMatch}
746731 query={props.searchQuery ?? ""}
747732 selected={visuallySelected}
......@@ -754,13 +739,8 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
754739 ) : (
755740 <RowPressable
756741 key={`${thread.environmentId}:${thread.id}`}
757 interactionClassName={
758 visuallySelected
759 ? Platform.OS === "android"
760 ? "bg-thread-selected-foreground"
761 : "bg-user-bubble-foreground"
762 : "bg-primary"
763 }
742 interactionClassName="bg-thread-hover"
743 interactionOpacity={visuallySelected ? 0 : 1}
764744 accessibilityHint="Opens the thread"
765745 accessibilityLabel={threadAccessibilityLabel}
766746 accessibilityRole="button"
......@@ -770,7 +750,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
770750 onSelectThread(thread);
771751 }}
772752 style={{
773 backgroundColor: visuallySelected ? effectiveSelectedBackground : backgroundColor,
753 backgroundColor: visuallySelected ? selectedBackgroundColor : backgroundColor,
774754 borderRadius: Platform.OS === "android" ? 20 : SIDEBAR_ROW_RADIUS,
775755 minHeight: 64,
776756 justifyContent: "center",
......@@ -784,10 +764,10 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
784764 className={cn(
785765 "flex-1 text-base font-t3-medium",
786766 visuallySelected
787 ? Platform.OS === "android"
788 ? "text-thread-selected-foreground"
789 : "text-user-bubble-foreground"
790 : "text-foreground",
767 ? "text-thread-selected-foreground"
768 : compact
769 ? "text-foreground"
770 : "text-drawer-foreground",
791771 )}
792772 numberOfLines={1}
793773 >
......@@ -802,10 +782,10 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
802782 className={cn(
803783 "text-xs tabular-nums",
804784 visuallySelected
805 ? Platform.OS === "android"
806 ? "text-thread-selected-foreground-muted"
807 : "text-user-bubble-foreground-muted"
808 : "text-foreground-muted",
785 ? "text-thread-selected-foreground-muted"
786 : compact
787 ? "text-foreground-muted"
788 : "text-drawer-foreground-muted",
809789 )}
810790 numberOfLines={1}
811791 >
......@@ -815,6 +795,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
815795 </View>
816796 {props.searchMatch ? (
817797 <ThreadSearchMatchExcerpt
798 sidebar={!compact}
818799 match={props.searchMatch}
819800 query={props.searchQuery ?? ""}
820801 selected={visuallySelected}
apps/mobile/src/features/threads/thread-list-v2-items.tsx+109-40
......@@ -64,7 +64,7 @@ const STATUS_LABEL_BY_STATUS: Partial<
6464 Record<ThreadListV2Status, { label: string; className: string }>
6565> = {
6666 approval: { label: "Approval", className: "text-warning-foreground" },
67 input: { label: "Input", className: "text-foreground-secondary" },
67 input: { label: "Input", className: "text-adaptive-indigo-600-300" },
6868 working: { label: "Working", className: "text-adaptive-sky-600-400" },
6969 failed: { label: "Failed", className: "text-danger-foreground" },
7070};
......@@ -112,6 +112,7 @@ function ThreadListV2Section(props: {
112112 };
113113}) {
114114 const snoozed = props.tone === "snoozed";
115 const sidebarPane = props.pane === "sidebar";
115116 const className = cn(
116117 "mb-1.5 mt-4 flex-row items-center gap-2.5",
117118 props.pane === "sidebar" ? "px-3" : "px-5",
......@@ -121,17 +122,32 @@ function ThreadListV2Section(props: {
121122 <Text
122123 className={cn(
123124 "text-xs font-t3-medium",
124 snoozed ? "text-foreground-secondary" : "text-foreground-tertiary",
125 sidebarPane
126 ? "text-drawer-foreground-muted"
127 : snoozed
128 ? "text-foreground-secondary"
129 : "text-foreground-tertiary",
125130 )}
126131 >
127132 {props.label}
128133 </Text>
129 <View className={cn("h-px flex-1", snoozed ? "bg-primary/20" : "bg-border")} />
134 <View
135 className={cn(
136 "h-px flex-1",
137 snoozed ? "bg-primary/20" : sidebarPane ? "bg-drawer-border" : "bg-border",
138 )}
139 />
130140 {props.disclosure ? (
131141 <SymbolView
132142 name="chevron.down"
133143 size={10}
134 tintColorClassName={snoozed ? "accent-icon-muted" : "accent-foreground-muted"}
144 tintColorClassName={
145 sidebarPane
146 ? "accent-drawer-foreground-muted"
147 : snoozed
148 ? "accent-icon-muted"
149 : "accent-foreground-muted"
150 }
135151 type="monochrome"
136152 style={{ transform: [{ rotate: props.disclosure.expanded ? "180deg" : "0deg" }] }}
137153 />
......@@ -209,6 +225,7 @@ export const ThreadListV2SettledShelfHeader = memo(function ThreadListV2SettledS
209225});
210226
211227export const ThreadListV2ShowMoreRow = memo(function ThreadListV2ShowMoreRow(props: {
228 readonly pane?: "screen" | "sidebar";
212229 readonly hiddenCount: number;
213230 readonly onPress: () => void;
214231}) {
......@@ -220,7 +237,13 @@ export const ThreadListV2ShowMoreRow = memo(function ThreadListV2ShowMoreRow(pro
220237 className="mx-4 mt-2 items-center rounded-lg border border-dashed border-border py-2.5"
221238 style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1 })}
222239 >
223 <Text className="text-xs font-t3-medium text-foreground-muted">
240 <Text
241 className={
242 props.pane === "sidebar"
243 ? "text-xs font-t3-medium text-drawer-foreground-muted"
244 : "text-xs font-t3-medium text-foreground-muted"
245 }
246 >
224247 Show more ({props.hiddenCount} settled hidden)
225248 </Text>
226249 </Pressable>
......@@ -282,7 +305,13 @@ export const ThreadListV2PendingRow = memo(function ThreadListV2PendingRow(props
282305 workspaceRoot={props.project.workspaceRoot}
283306 />
284307 ) : null}
285 <Text className="flex-1 text-sm font-t3-medium text-foreground-muted" numberOfLines={1}>
308 <Text
309 className={cn(
310 "flex-1 text-sm font-t3-medium text-foreground-muted",
311 sidebarPane && "text-drawer-foreground-muted",
312 )}
313 numberOfLines={1}
314 >
286315 {projectTitle}
287316 </Text>
288317 {isDraft ? (
......@@ -296,33 +325,67 @@ export const ThreadListV2PendingRow = memo(function ThreadListV2PendingRow(props
296325 <Text className="text-xs text-adaptive-amber-700-300">Draft</Text>
297326 </View>
298327 ) : (
299 <Text className="text-xs text-foreground-tertiary">Sends on reconnect</Text>
328 <Text
329 className={cn(
330 "text-xs text-foreground-tertiary",
331 sidebarPane && "text-drawer-foreground-muted",
332 )}
333 >
334 Sends on reconnect
335 </Text>
300336 )}
301337 </View>
302338 {/* One line, unlike the two an active row allows: a queued title is
303339 derived from the whole prompt rather than written as a title, so the
304340 second line is usually a stray word or emoji rather than meaning. */}
305 <Text className="mt-1 text-base font-t3-medium text-foreground" numberOfLines={1}>
341 <Text
342 className={cn(
343 "mt-1 text-base font-t3-medium text-foreground",
344 sidebarPane && "text-drawer-foreground",
345 )}
346 numberOfLines={1}
347 >
306348 {pendingTask.title}
307349 </Text>
308350 {branch || props.environmentLabel ? (
309351 <View className="mt-1 flex-row items-center gap-1">
310 <Text className="shrink text-xs text-foreground-muted" numberOfLines={1}>
352 <Text
353 className={cn(
354 "shrink text-xs text-foreground-muted",
355 sidebarPane && "text-drawer-foreground-muted",
356 )}
357 numberOfLines={1}
358 >
311359 {branch ? (
312 <Text className="text-xs text-foreground-muted" style={{ fontFamily: MONO_FONT }}>
360 <Text
361 className={cn(
362 "text-xs text-foreground-muted",
363 sidebarPane && "text-drawer-foreground-muted",
364 )}
365 style={{ fontFamily: MONO_FONT }}
366 >
313367 {branch}
314368 </Text>
315369 ) : null}
316370 {branch && props.environmentLabel ? " · " : null}
317371 {props.environmentLabel ? (
318 <Text className="text-xs text-foreground-tertiary">{props.environmentLabel}</Text>
372 <Text
373 className={cn(
374 "text-xs text-foreground-tertiary",
375 sidebarPane && "text-drawer-foreground-muted",
376 )}
377 >
378 {props.environmentLabel}
379 </Text>
319380 ) : null}
320381 </Text>
321382 {props.environmentLabel && props.environmentMachine ? (
322383 <EnvironmentMachineSymbol
323384 kind={props.environmentMachine}
324385 size={11}
325 tintColorClassName="accent-foreground-tertiary"
386 tintColorClassName={
387 sidebarPane ? "accent-drawer-foreground-muted" : "accent-foreground-tertiary"
388 }
326389 />
327390 ) : null}
328391 </View>
......@@ -350,6 +413,7 @@ export const ThreadListV2PendingRow = memo(function ThreadListV2PendingRow(props
350413 accessibilityRole="button"
351414 key={pendingTask.key}
352415 className={sidebarPane ? "bg-drawer" : "bg-screen"}
416 interactionClassName={sidebarPane ? "bg-thread-hover" : "bg-row-hover"}
353417 onPress={() => onSelectPendingTask(pendingTask)}
354418 style={
355419 sidebarPane
......@@ -759,8 +823,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
759823 ? `Opens the thread. Swipe left to ${primaryAction.label.toLowerCase()}.`
760824 : `Opens the thread. Swipe left for ${primaryAction.label.toLowerCase()} and snooze actions.`;
761825
762 // The sidebar pane fills selected rows with the theme's message surface, so
763 // every piece of row text must use that surface's paired foreground.
826 // Sidebar rows use navigation foregrounds on their active and idle surfaces.
764827 const cardContent = (
765828 <>
766829 <View className="flex-row items-center gap-1.5">
......@@ -776,7 +839,9 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
776839 <Text
777840 className={cn(
778841 "flex-1 text-sm font-t3-medium",
779 selected ? selectedThreadRowColors.mutedForegroundClassName : "text-foreground-muted",
842 selected
843 ? selectedThreadRowColors.mutedForegroundClassName
844 : rowAppearance.mutedForegroundClassName,
780845 )}
781846 numberOfLines={1}
782847 >
......@@ -787,16 +852,17 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
787852 <SymbolView
788853 name="pin"
789854 size={11}
790 tintColorClassName="accent-foreground-muted"
855 tintColorClassName={rowAppearance.mutedIconTintClassName}
791856 type="monochrome"
792857 />
793858 ) : null}
794859 <Text
795860 className={cn(
796861 "text-xs tabular-nums",
797 selected
798 ? selectedThreadRowColors.foregroundClassName
799 : (statusLabel?.className ?? "text-foreground-tertiary"),
862 statusLabel?.className ??
863 (selected
864 ? selectedThreadRowColors.foregroundClassName
865 : rowAppearance.tertiaryForegroundClassName),
800866 )}
801867 >
802868 {statusLabel?.label ?? timeLabel}
......@@ -805,7 +871,9 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
805871 <Text
806872 className={cn(
807873 "mt-1 text-base font-t3-medium",
808 selected ? selectedThreadRowColors.foregroundClassName : "text-foreground",
874 selected
875 ? selectedThreadRowColors.foregroundClassName
876 : rowAppearance.foregroundClassName,
809877 )}
810878 numberOfLines={2}
811879 >
......@@ -814,6 +882,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
814882 {props.searchMatch ? (
815883 <View className="mt-1">
816884 <ThreadSearchMatchExcerpt
885 sidebar={sidebarPane}
817886 match={props.searchMatch}
818887 query={props.searchQuery ?? ""}
819888 selected={selected}
......@@ -846,7 +915,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
846915 "shrink text-xs",
847916 selected
848917 ? selectedThreadRowColors.mutedForegroundClassName
849 : "text-foreground-muted",
918 : rowAppearance.mutedForegroundClassName,
850919 )}
851920 numberOfLines={1}
852921 >
......@@ -856,7 +925,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
856925 "text-xs",
857926 selected
858927 ? selectedThreadRowColors.mutedForegroundClassName
859 : "text-foreground-muted",
928 : rowAppearance.mutedForegroundClassName,
860929 )}
861930 style={{ fontFamily: MONO_FONT }}
862931 >
......@@ -870,7 +939,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
870939 "text-xs",
871940 selected
872941 ? selectedThreadRowColors.mutedForegroundClassName
873 : "text-foreground-tertiary",
942 : rowAppearance.tertiaryForegroundClassName,
874943 )}
875944 >
876945 {props.environmentLabel}
......@@ -884,7 +953,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
884953 tintColorClassName={
885954 selected
886955 ? selectedThreadRowColors.mutedIconTintClassName
887 : "accent-foreground-tertiary"
956 : rowAppearance.tertiaryIconTintClassName
888957 }
889958 />
890959 ) : null}
......@@ -899,24 +968,19 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
899968 name={pr.kind === "stack" ? "square.3.layers.3d" : "arrow.triangle.pull"}
900969 size={12}
901970 tintColorClassName={
902 selected
903 ? selectedThreadRowColors.iconTintClassName
904 : pr.state === null || pr.isDraft
905 ? "accent-foreground-muted"
906 : pr.state === "open"
907 ? "accent-adaptive-emerald-600-400"
908 : pr.state === "closed"
909 ? "accent-adaptive-rose-600-400"
910 : "accent-adaptive-violet-600-400"
971 pr.state === null || pr.isDraft
972 ? rowAppearance.mutedIconTintClassName
973 : pr.state === "open"
974 ? "accent-adaptive-emerald-600-400"
975 : pr.state === "closed"
976 ? "accent-adaptive-rose-600-400"
977 : "accent-adaptive-violet-600-400"
911978 }
912979 />
913980 ) : null}
914981 <Text
915982 accessibilityLabel={pr.accessibilityLabel}
916 className={cn(
917 "text-xs",
918 selected ? selectedThreadRowColors.foregroundClassName : pr.textClassName,
919 )}
983 className={cn("text-xs", pr.textClassName)}
920984 style={{ fontFamily: MONO_FONT }}
921985 >
922986 {pr.kind === "stack" || pr.others > 0 ? pr.label : `#${pr.label}`}
......@@ -942,6 +1006,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
9421006 <RowPressable
9431007 key={`${thread.environmentId}:${thread.id}`}
9441008 interactionClassName={rowAppearance.interactionClassName}
1009 interactionOpacity={rowAppearance.interactionOpacity}
9451010 className={rowAppearance.className}
9461011 accessibilityHint={swipeAccessibilityHint}
9471012 accessibilityLabel={
......@@ -974,6 +1039,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
9741039 <RowPressable
9751040 key={`${thread.environmentId}:${thread.id}`}
9761041 interactionClassName={rowAppearance.interactionClassName}
1042 interactionOpacity={rowAppearance.interactionOpacity}
9771043 accessibilityHint={swipeAccessibilityHint}
9781044 accessibilityLabel={
9791045 props.hasQueuedMessages ? `${thread.title}, messages queued to send` : thread.title
......@@ -1009,7 +1075,9 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
10091075 <Text
10101076 className={cn(
10111077 "text-base",
1012 selected ? selectedThreadRowColors.foregroundClassName : "text-foreground-muted",
1078 selected
1079 ? selectedThreadRowColors.foregroundClassName
1080 : rowAppearance.mutedForegroundClassName,
10131081 )}
10141082 numberOfLines={1}
10151083 >
......@@ -1017,6 +1085,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
10171085 </Text>
10181086 {props.searchMatch ? (
10191087 <ThreadSearchMatchExcerpt
1088 sidebar={sidebarPane}
10201089 match={props.searchMatch}
10211090 query={props.searchQuery ?? ""}
10221091 selected={selected}
......@@ -1030,8 +1099,8 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
10301099 selected
10311100 ? selectedThreadRowColors.mutedForegroundClassName
10321101 : snoozedRow
1033 ? "text-foreground-secondary"
1034 : "text-foreground-tertiary",
1102 ? rowAppearance.mutedForegroundClassName
1103 : rowAppearance.tertiaryForegroundClassName,
10351104 )}
10361105 style={{ fontFamily: MONO_FONT }}
10371106 >
apps/mobile/src/features/threads/thread-list-v2-row-appearance.android.ts+19-5
......@@ -22,8 +22,9 @@ export function getThreadListV2RowAppearance(
2222 selected: boolean,
2323) {
2424 const selectedBackgroundColor = theme["--color-thread-selected"];
25 const backgroundColor = theme[sidebarPane ? "--color-drawer" : "--color-screen"];
2526 const style: ViewStyle = {
26 backgroundColor: selected ? selectedBackgroundColor : theme["--color-screen"],
27 backgroundColor: selected ? selectedBackgroundColor : backgroundColor,
2728 borderRadius: 20,
2829 };
2930 const swipeContainerStyle: ViewStyle = {
......@@ -35,12 +36,25 @@ export function getThreadListV2RowAppearance(
3536
3637 return {
3738 className: undefined,
38 interactionClassName: selected ? "bg-thread-selected-foreground" : "bg-primary",
39 interactionClassName: sidebarPane ? "bg-thread-hover" : "bg-row-hover",
40 interactionOpacity: selected ? 0 : 1,
41 foregroundClassName: sidebarPane ? "text-drawer-foreground" : "text-foreground",
42 mutedForegroundClassName: sidebarPane
43 ? "text-drawer-foreground-muted"
44 : "text-foreground-muted",
45 tertiaryForegroundClassName: sidebarPane
46 ? "text-drawer-foreground-muted"
47 : "text-foreground-tertiary",
48 mutedIconTintClassName: sidebarPane
49 ? "accent-drawer-foreground-muted"
50 : "accent-foreground-muted",
51 tertiaryIconTintClassName: sidebarPane
52 ? "accent-drawer-foreground-muted"
53 : "accent-foreground-tertiary",
3954 style,
4055 cardStyle: sidebarPane ? { ...style, paddingHorizontal: 12, paddingVertical: 10 } : style,
4156 swipeContainerStyle,
42 swipeBackgroundColor: theme["--color-screen"],
43 providerIconSurfaceColor:
44 sidebarPane && selected ? selectedBackgroundColor : theme["--color-screen"],
57 swipeBackgroundColor: backgroundColor,
58 providerIconSurfaceColor: selected ? selectedBackgroundColor : backgroundColor,
4559 };
4660}
apps/mobile/src/features/threads/thread-list-v2-row-appearance.test.ts created+37
......@@ -0,0 +1,37 @@
1import { describe, expect, it } from "vite-plus/test";
2import { MOBILE_THEME_IDS } from "@t3tools/shared/themePalettes";
3
4import { getMobileThemeVariables } from "../../lib/mobileTheme";
5import { getThreadListV2RowAppearance as iosAppearance } from "./thread-list-v2-row-appearance";
6import { getThreadListV2RowAppearance as androidAppearance } from "./thread-list-v2-row-appearance.android";
7
8describe.each([
9 ["ios", iosAppearance],
10 ["android", androidAppearance],
11] as const)("%s thread row colors", (_platform, appearanceFor) => {
12 it.each(MOBILE_THEME_IDS)(
13 "preserves active selection and uses neutral hover for %s",
14 (themeId) => {
15 for (const appearance of ["light", "dark"] as const) {
16 const theme = getMobileThemeVariables(themeId, appearance);
17 const idle = appearanceFor(theme, true, false);
18 const active = appearanceFor(theme, true, true);
19
20 expect(idle.style?.backgroundColor).toBe(theme["--color-drawer"]);
21 expect(idle.interactionClassName).toBe("bg-thread-hover");
22 expect(idle.interactionOpacity).toBe(1);
23 expect(active.style?.backgroundColor).toBe(theme["--color-thread-selected"]);
24 // Pointer feedback must not mix a second color into the active background.
25 expect(active.interactionOpacity).toBe(0);
26 expect(active.providerIconSurfaceColor).toBe(active.style?.backgroundColor);
27 expect(idle.foregroundClassName).toBe("text-drawer-foreground");
28 expect(idle.mutedForegroundClassName).toBe("text-drawer-foreground-muted");
29
30 const phone = appearanceFor(theme, false, false);
31 expect(phone.swipeBackgroundColor).toBe(theme["--color-screen"]);
32 expect(phone.interactionClassName).toBe("bg-row-hover");
33 expect(phone.foregroundClassName).toBe("text-foreground");
34 }
35 },
36 );
37});
apps/mobile/src/features/threads/thread-list-v2-row-appearance.ts+20-6
......@@ -6,10 +6,10 @@ export const THREAD_LIST_V2_ROW_CONTENT_CLASS_NAME = "px-5 py-2.5";
66export const THREAD_LIST_V2_ROW_DIVIDERS = true;
77
88export const selectedThreadRowColors = {
9 foregroundClassName: "text-user-bubble-foreground",
10 mutedForegroundClassName: "text-user-bubble-foreground-muted",
11 iconTintClassName: "accent-user-bubble-foreground",
12 mutedIconTintClassName: "accent-user-bubble-foreground-muted",
9 foregroundClassName: "text-thread-selected-foreground",
10 mutedForegroundClassName: "text-thread-selected-foreground-muted",
11 iconTintClassName: "accent-thread-selected-foreground",
12 mutedIconTintClassName: "accent-thread-selected-foreground-muted",
1313};
1414
1515export function getThreadListV2NewBranchMenuTitle(_branch: string) {
......@@ -21,7 +21,7 @@ export function getThreadListV2RowAppearance(
2121 sidebarPane: boolean,
2222 selected: boolean,
2323) {
24 const selectedBackgroundColor = theme["--color-user-bubble"];
24 const selectedBackgroundColor = theme["--color-thread-selected"];
2525 const style: ViewStyle | undefined = sidebarPane
2626 ? {
2727 backgroundColor: selected ? selectedBackgroundColor : theme["--color-drawer"],
......@@ -34,7 +34,21 @@ export function getThreadListV2RowAppearance(
3434
3535 return {
3636 className: sidebarPane ? undefined : "bg-screen",
37 interactionClassName: selected && sidebarPane ? "bg-user-bubble-foreground" : "bg-primary",
37 interactionClassName: sidebarPane ? "bg-thread-hover" : "bg-row-hover",
38 interactionOpacity: selected ? 0 : 1,
39 foregroundClassName: sidebarPane ? "text-drawer-foreground" : "text-foreground",
40 mutedForegroundClassName: sidebarPane
41 ? "text-drawer-foreground-muted"
42 : "text-foreground-muted",
43 tertiaryForegroundClassName: sidebarPane
44 ? "text-drawer-foreground-muted"
45 : "text-foreground-tertiary",
46 mutedIconTintClassName: sidebarPane
47 ? "accent-drawer-foreground-muted"
48 : "accent-foreground-muted",
49 tertiaryIconTintClassName: sidebarPane
50 ? "accent-drawer-foreground-muted"
51 : "accent-foreground-tertiary",
3852 style,
3953 cardStyle: sidebarPane ? { ...style, paddingHorizontal: 12, paddingVertical: 10 } : undefined,
4054 swipeContainerStyle,
apps/mobile/src/features/threads/thread-search-match.tsx+16-14
......@@ -1,4 +1,3 @@
1import { Platform } from "react-native";
21import type { EnvironmentThreadSearchMatch } from "@t3tools/client-runtime/state/thread-search";
32
43import { AppText as Text } from "../../components/AppText";
......@@ -49,6 +48,7 @@ export function ThreadSearchMatchExcerpt(props: {
4948 readonly query: string;
5049 readonly selected?: boolean;
5150 readonly compact?: boolean;
51 readonly sidebar?: boolean;
5252}) {
5353 const isUser = props.match.source === "user";
5454 const parts = splitHighlightParts(props.match.snippet, props.query);
......@@ -57,10 +57,10 @@ export function ThreadSearchMatchExcerpt(props: {
5757 className={cn(
5858 props.compact ? "text-sm" : "text-xs",
5959 props.selected
60 ? Platform.OS === "android"
61 ? "text-thread-selected-foreground-muted"
62 : "text-user-bubble-foreground-muted"
63 : "text-foreground-muted",
60 ? "text-thread-selected-foreground-muted"
61 : props.sidebar
62 ? "text-drawer-foreground-muted"
63 : "text-foreground-muted",
6464 )}
6565 numberOfLines={1}
6666 >
......@@ -68,11 +68,11 @@ export function ThreadSearchMatchExcerpt(props: {
6868 className={cn(
6969 props.compact ? "text-sm font-t3-medium" : "text-xs font-t3-medium",
7070 props.selected
71 ? Platform.OS === "android"
72 ? "text-thread-selected-foreground"
73 : "text-user-bubble-foreground"
71 ? "text-thread-selected-foreground"
7472 : isUser
75 ? "text-foreground-secondary"
73 ? props.sidebar
74 ? "text-drawer-foreground-muted"
75 : "text-foreground-secondary"
7676 : "text-adaptive-emerald-600-400",
7777 )}
7878 >
......@@ -84,12 +84,14 @@ export function ThreadSearchMatchExcerpt(props: {
8484 props.compact ? "text-sm" : "text-xs",
8585 part.highlighted && "font-t3-bold",
8686 props.selected
87 ? Platform.OS === "android"
88 ? "text-thread-selected-foreground"
89 : "text-user-bubble-foreground"
87 ? "text-thread-selected-foreground"
9088 : part.highlighted
91 ? "text-foreground"
92 : "text-foreground-muted",
89 ? props.sidebar
90 ? "text-drawer-foreground"
91 : "text-foreground"
92 : props.sidebar
93 ? "text-drawer-foreground-muted"
94 : "text-foreground-muted",
9395 )}
9496 key={part.start}
9597 >
apps/mobile/src/features/threads/thread-work-log.tsx+3-3
......@@ -882,7 +882,7 @@ const ThreadWorkLogRow = memo(function ThreadWorkLogRow(
882882 entering={WORK_LOG_DETAIL_ENTER_TRANSITION}
883883 exiting={WORK_LOG_DETAIL_EXIT_TRANSITION}
884884 layout={WORK_LOG_LAYOUT_TRANSITION}
885 className="ml-7 border-l border-adaptive-neutral-300-a60-white-a12 pb-1 pl-3 pt-0.5"
885 className="ml-7 border-l border-border pb-1 pl-3 pt-0.5"
886886 >
887887 {row.workEntry.questionAnswer ? (
888888 <QuestionAnswerHistory
......@@ -1037,7 +1037,7 @@ export const ThreadAgentSpawnCard = memo(function ThreadAgentSpawnCard(props: {
10371037 props.onToggle();
10381038 }}
10391039 onLongPress={props.onCopy}
1040 className="rounded-xl border border-adaptive-neutral-200-a80-white-a8 bg-card px-2.5 py-2 active:bg-subtle"
1040 className="rounded-xl border border-border-subtle bg-card px-2.5 py-2 active:bg-subtle"
10411041 >
10421042 <View className="flex-row items-center gap-2">
10431043 <View className="h-6 w-6 shrink-0 items-center justify-center">
......@@ -1094,7 +1094,7 @@ export const ThreadAgentSpawnCard = memo(function ThreadAgentSpawnCard(props: {
10941094 entering={WORK_LOG_DETAIL_ENTER_TRANSITION}
10951095 exiting={WORK_LOG_DETAIL_EXIT_TRANSITION}
10961096 layout={WORK_LOG_LAYOUT_TRANSITION}
1097 className="ml-8 mt-1.5 gap-1.5 border-l border-adaptive-neutral-300-a60-white-a12 pl-3"
1097 className="ml-8 mt-1.5 gap-1.5 border-l border-border pl-3"
10981098 >
10991099 {summary.members.map((member) => (
11001100 <View key={member.title} className="gap-px">
apps/mobile/src/features/threads/threadPresentation.ts+7-23
......@@ -12,10 +12,6 @@ export type ThreadStatusKind =
1212
1313export interface ThreadStatusPresentation extends StatusTone {
1414 readonly kind: ThreadStatusKind;
15 /** Foreground color for the leading status icon. */
16 readonly iconColor: string;
17 /** Background color for the leading status icon circle. */
18 readonly iconBackground: string;
1915 /** Whether the indicator represents in-flight activity. */
2016 readonly pulse: boolean;
2117}
......@@ -44,8 +40,6 @@ export function resolveThreadStatus(
4440 label: "Needs Approval",
4541 pillClassName: "bg-warning",
4642 textClassName: "text-warning-foreground",
47 iconColor: "#ff9f0a",
48 iconBackground: "rgba(255,159,10,0.22)",
4943 pulse: false,
5044 };
5145 }
......@@ -54,10 +48,8 @@ export function resolveThreadStatus(
5448 return {
5549 kind: "awaiting-input",
5650 label: "Awaiting Input",
57 pillClassName: "bg-primary/10",
58 textClassName: "text-foreground-secondary",
59 iconColor: "#5e5ce6",
60 iconBackground: "rgba(94,92,230,0.22)",
51 pillClassName: "bg-adaptive-indigo-500-a12-a16",
52 textClassName: "text-adaptive-indigo-600-300",
6153 pulse: false,
6254 };
6355 }
......@@ -66,10 +58,8 @@ export function resolveThreadStatus(
6658 return {
6759 kind: "working",
6860 label: "Working",
69 pillClassName: "bg-primary/10",
61 pillClassName: "bg-adaptive-sky-500-a12-a16",
7062 textClassName: "text-adaptive-sky-600-400",
71 iconColor: "#0a84ff",
72 iconBackground: "rgba(10,132,255,0.22)",
7363 pulse: true,
7464 };
7565 }
......@@ -78,10 +68,8 @@ export function resolveThreadStatus(
7868 return {
7969 kind: "connecting",
8070 label: "Connecting",
81 pillClassName: "bg-primary/10",
82 textClassName: "text-foreground-secondary",
83 iconColor: "#0a84ff",
84 iconBackground: "rgba(10,132,255,0.22)",
71 pillClassName: "bg-adaptive-sky-500-a12-a16",
72 textClassName: "text-adaptive-sky-600-400",
8573 pulse: true,
8674 };
8775 }
......@@ -92,8 +80,6 @@ export function resolveThreadStatus(
9280 label: "Error",
9381 pillClassName: "bg-danger",
9482 textClassName: "text-danger-foreground",
95 iconColor: "#ff453a",
96 iconBackground: "rgba(255,69,58,0.22)",
9783 pulse: false,
9884 };
9985 }
......@@ -106,10 +92,8 @@ export function resolveThreadStatus(
10692 return {
10793 kind: "plan-ready",
10894 label: "Plan Ready",
109 pillClassName: "bg-primary/10",
110 textClassName: "text-foreground-secondary",
111 iconColor: "#bf5af2",
112 iconBackground: "rgba(191,90,242,0.22)",
95 pillClassName: "bg-adaptive-violet-500-a12-a16",
96 textClassName: "text-adaptive-violet-600-400",
11397 pulse: false,
11498 };
11599 }
apps/mobile/src/features/threads/worktree-setup-card.tsx+26-28
......@@ -72,7 +72,7 @@ export function WorktreeSetupCard(props: WorktreeSetupCardProps) {
7272 />
7373 {!handedOff ? (
7474 <Text
75 className="text-2xs text-adaptive-neutral-600-400"
75 className="text-2xs text-foreground-secondary"
7676 style={{ fontVariant: ["tabular-nums"] }}
7777 >
7878 {elapsed(snapshot.startedAt, snapshot.endedAt, now)}
......@@ -99,25 +99,21 @@ export function WorktreeSetupCard(props: WorktreeSetupCardProps) {
9999 {backgroundSetup ? (
100100 <ActivityIndicator
101101 size="small"
102 colorClassName="accent-adaptive-neutral-500-400"
102 colorClassName="accent-icon-muted"
103103 style={{ width: 12, height: 12, transform: [{ scale: 0.65 }] }}
104104 />
105105 ) : failed ? (
106106 <SymbolView
107107 name="exclamationmark.circle"
108108 size={12}
109 tintColorClassName="accent-adaptive-rose-700-300"
109 tintColorClassName="accent-danger-foreground"
110110 />
111111 ) : null}
112 <Text numberOfLines={1} className="shrink text-2xs text-adaptive-neutral-600-400">
112 <Text numberOfLines={1} className="shrink text-2xs text-foreground-secondary">
113113 {backgroundSetup ? scriptName : "Details"}
114114 </Text>
115115 {!backgroundSetup ? (
116 <SymbolView
117 name="chevron.right"
118 size={10}
119 tintColorClassName="accent-adaptive-neutral-500-400"
120 />
116 <SymbolView name="chevron.right" size={10} tintColorClassName="accent-icon-muted" />
121117 ) : null}
122118 </View>
123119 </Pressable>
......@@ -166,14 +162,19 @@ function HeaderLabel({
166162 failed?: boolean;
167163}) {
168164 return active ? (
169 <ShimmeringWorkContent icon="clock" iconSubtleColor="#737373" label={label} showIcon={false} />
165 <ShimmeringWorkContent
166 icon="clock"
167 iconSubtleColor="transparent"
168 label={label}
169 showIcon={false}
170 />
170171 ) : (
171172 <Text
172173 numberOfLines={1}
173174 className={
174175 failed
175 ? "flex-1 text-sm text-adaptive-rose-700-300"
176 : "flex-1 text-sm text-adaptive-neutral-600-400"
176 ? "flex-1 text-sm text-danger-foreground"
177 : "flex-1 text-sm text-foreground-secondary"
177178 }
178179 style={{ fontVariant: ["tabular-nums"] }}
179180 >
......@@ -232,7 +233,7 @@ function SetupDetailsSheet({
232233 </View>
233234 ))}
234235 {snapshot.phase === "failed" && snapshot.error ? (
235 <Text selectable className="ml-8 mt-2 text-xs text-adaptive-rose-700-300">
236 <Text selectable className="ml-8 mt-2 text-xs text-danger-foreground">
236237 {snapshot.error}
237238 </Text>
238239 ) : null}
......@@ -303,7 +304,7 @@ function StageRow({
303304 {stage.status === "running" && animate ? (
304305 <ActivityIndicator
305306 size="small"
306 colorClassName="accent-adaptive-neutral-500-400"
307 colorClassName="accent-icon-muted"
307308 style={{ transform: [{ scale: 0.75 }] }}
308309 />
309310 ) : (
......@@ -312,10 +313,10 @@ function StageRow({
312313 size={14}
313314 tintColorClassName={
314315 stage.status === "failed"
315 ? "accent-adaptive-rose-700-300"
316 ? "accent-danger-foreground"
316317 : stage.status === "warning"
317318 ? "accent-warning-foreground"
318 : "accent-adaptive-neutral-500-400"
319 : "accent-icon-muted"
319320 }
320321 />
321322 )}
......@@ -323,7 +324,7 @@ function StageRow({
323324 {stage.status === "running" && animate ? (
324325 <ShimmeringWorkContent
325326 icon="clock"
326 iconSubtleColor="#737373"
327 iconSubtleColor="transparent"
327328 label={label}
328329 showIcon={false}
329330 />
......@@ -332,24 +333,21 @@ function StageRow({
332333 numberOfLines={1}
333334 className={
334335 stage.status === "failed"
335 ? "flex-1 text-sm text-adaptive-rose-700-300"
336 : "flex-1 text-sm text-adaptive-neutral-600-400"
336 ? "flex-1 text-sm text-danger-foreground"
337 : "flex-1 text-sm text-foreground-secondary"
337338 }
338339 >
339340 {label}
340341 </Text>
341342 )}
342343 {detail ? (
343 <Text
344 numberOfLines={1}
345 className="max-w-[25%] shrink text-2xs text-adaptive-neutral-600-400"
346 >
344 <Text numberOfLines={1} className="max-w-[25%] shrink text-2xs text-foreground-secondary">
347345 {detail}
348346 </Text>
349347 ) : null}
350348 {stage.status !== "pending" && stage.status !== "skipped" ? (
351349 <Text
352 className="text-2xs text-adaptive-neutral-600-400"
350 className="text-2xs text-foreground-secondary"
353351 style={{ fontVariant: ["tabular-nums"] }}
354352 >
355353 {elapsed(stage.startedAt, stage.endedAt, now)}
......@@ -368,8 +366,8 @@ function OutputTail({ lines, failed }: { lines: ReadonlyArray<string>; failed: b
368366 accessibilityLabel="Setup script output"
369367 className={
370368 failed
371 ? "mb-2 ml-8 rounded-md border border-adaptive-rose-700-300/20 bg-adaptive-rose-50-950 px-3 py-2"
372 : "mb-2 ml-8 rounded-md border border-border bg-adaptive-neutral-100-900 px-3 py-2"
369 ? "mb-2 ml-8 rounded-md border border-danger-border bg-danger px-3 py-2"
370 : "mb-2 ml-8 rounded-md border border-border bg-card-alt px-3 py-2"
373371 }
374372 >
375373 {OUTPUT_TAIL_SLOTS.map((slot) => (
......@@ -379,8 +377,8 @@ function OutputTail({ lines, failed }: { lines: ReadonlyArray<string>; failed: b
379377 numberOfLines={1}
380378 className={
381379 failed
382 ? "text-2xs leading-5 text-adaptive-rose-700-300 ios:font-[family-name:Menlo] android:font-mono"
383 : "text-2xs leading-5 text-adaptive-neutral-600-400 ios:font-[family-name:Menlo] android:font-mono"
380 ? "text-2xs leading-5 text-danger-foreground ios:font-[family-name:Menlo] android:font-mono"
381 : "text-2xs leading-5 text-foreground-secondary ios:font-[family-name:Menlo] android:font-mono"
384382 }
385383 >
386384 {lines[lines.length - OUTPUT_TAIL_SLOTS.length + slot] || "\u00a0"}
apps/mobile/src/features/voice-input/ComposerDictationControl.tsx+1-1
......@@ -300,7 +300,7 @@ export function ComposerDictationStatus(props: {
300300 <View className="relative h-11 min-w-0 flex-1 justify-center">
301301 {isError ? (
302302 <View className="min-w-0 flex-row items-center gap-1.5 px-2">
303 <Text className="min-w-0 flex-1 text-sm text-red-400" numberOfLines={2}>
303 <Text className="min-w-0 flex-1 text-sm text-danger-foreground" numberOfLines={2}>
304304 {props.presentation.statusLabel}
305305 </Text>
306306 <Pressable
apps/mobile/src/lib/materialYouTheme.android.ts+12
......@@ -36,12 +36,15 @@ export function materialYouPaletteToMobileThemeVariables(
3636 "--color-sheet": withAlpha(palette.surfaceContainerLow, 0.98),
3737 "--color-sheet-solid": palette.surfaceContainerLow,
3838 "--color-card": palette.surfaceContainer,
39 "--color-grouped-card": palette.surfaceContainer,
3940 "--color-card-alt": palette.surfaceContainerHigh,
4041 "--color-card-translucent": withAlpha(palette.surfaceContainer, 0.8),
4142 "--color-thread-canvas": palette.surfaceContainerLow,
4243 "--color-thread-selected": palette.surfaceContainer,
4344 "--color-thread-selected-foreground": palette.onSurface,
4445 "--color-thread-selected-foreground-muted": palette.onSurfaceVariant,
46 "--color-thread-hover": palette.surfaceContainerHigh,
47 "--color-row-hover": palette.surfaceContainerHigh,
4548 "--color-composer-panel": withAlpha(palette.surfaceContainerLow, dark ? 0.92 : 0.88),
4649 "--color-composer-surface": withAlpha(palette.surfaceContainerHigh, dark ? 0.9 : 0.94),
4750 "--color-composer-border": withAlpha(palette.outlineVariant, dark ? 0.34 : 0.46),
......@@ -50,6 +53,7 @@ export function materialYouPaletteToMobileThemeVariables(
5053 "--color-foreground-muted": palette.onSurfaceVariant,
5154 "--color-foreground-tertiary": withAlpha(palette.onSurfaceVariant, 0.58),
5255 "--color-border": withAlpha(palette.outlineVariant, dark ? 0.5 : 0.65),
56 "--color-focus": palette.primary,
5357 "--color-border-subtle": withAlpha(palette.outlineVariant, dark ? 0.34 : 0.46),
5458 "--color-separator": withAlpha(palette.outlineVariant, dark ? 0.24 : 0.34),
5559 "--color-subtle": withAlpha(palette.onSurface, 0.05),
......@@ -59,6 +63,7 @@ export function materialYouPaletteToMobileThemeVariables(
5963 "--color-inline-skill-foreground": palette.onTertiaryContainer,
6064 "--color-primary": palette.primary,
6165 "--color-primary-foreground": palette.onPrimary,
66 "--color-primary-text": palette.primary,
6267 "--color-primary-shadow": withAlpha(palette.scrim, dark ? 0.22 : 0.18),
6368 "--color-secondary": palette.secondaryContainer,
6469 "--color-secondary-foreground": palette.onSecondaryContainer,
......@@ -70,6 +75,8 @@ export function materialYouPaletteToMobileThemeVariables(
7075 "--color-danger": palette.errorContainer,
7176 "--color-danger-border": withAlpha(palette.onErrorContainer, dark ? 0.42 : 0.5),
7277 "--color-danger-foreground": palette.onErrorContainer,
78 "--color-update": palette.primaryContainer,
79 "--color-update-foreground": palette.onPrimaryContainer,
7380 "--color-input": palette.surfaceContainerLowest,
7481 "--color-input-border": withAlpha(palette.outline, dark ? 0.42 : 0.5),
7582 "--color-sidebar-search": palette.surfaceContainerLowest,
......@@ -78,8 +85,10 @@ export function materialYouPaletteToMobileThemeVariables(
7885 "--color-icon-muted": palette.onSurfaceVariant,
7986 "--color-icon-subtle": withAlpha(palette.onSurfaceVariant, 0.68),
8087 "--color-header": palette.surfaceContainerHigh,
88 "--color-header-foreground": palette.onSurface,
8189 "--color-header-border": withAlpha(palette.outlineVariant, dark ? 0.36 : 0.48),
8290 "--color-glass-surface": withAlpha(palette.surfaceContainerHigh, dark ? 0.78 : 0.72),
91 "--color-glass-fallback": withAlpha(palette.surfaceContainerHigh, dark ? 0.9 : 0.94),
8392 "--color-glass-tint": withAlpha(palette.primaryContainer, dark ? 0.24 : 0.18),
8493 "--color-status-bar": palette.surfaceContainerLow,
8594 "--color-md-body": materialTone(palette.onSurfaceVariant, dark ? 90 : 10),
......@@ -100,6 +109,9 @@ export function materialYouPaletteToMobileThemeVariables(
100109 "--color-user-bubble-skill-foreground": userBubbleForeground,
101110 "--color-backdrop": withAlpha(palette.scrim, dark ? 0.48 : 0.22),
102111 "--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),
103115 "--color-drawer-shadow": withAlpha(palette.scrim, dark ? 0.32 : 0.12),
104116 "--color-dot-separator": withAlpha(palette.onSurface, 0.2),
105117 "--color-wordmark": palette.onSurface,
apps/mobile/src/lib/materialYouTheme.test.ts+1
......@@ -54,6 +54,7 @@ describe("Material You system colors", () => {
5454 expect(variables["--color-thread-canvas"]).toBe(palette.surfaceContainerLow);
5555 expect(variables["--color-thread-selected"]).toBe(palette.surfaceContainer);
5656 expect(variables["--color-header"]).toBe(palette.surfaceContainerHigh);
57 expect(variables["--color-grouped-card"]).toBe(palette.surfaceContainer);
5758 expect(variables["--color-primary"]).toBe(palette.primary);
5859 expect(variables["--color-placeholder"]).toBe("#1C1B1F9E");
5960 });
apps/mobile/src/lib/mobileTheme.test-support.ts+13-4
......@@ -1,20 +1,29 @@
11import * as NodeFS from "node:fs";
22
3import type { MobileThemeAppearance, MobileThemeVariables } from "./mobileTheme";
3import {
4 MOBILE_THEME_VARIABLE_NAMES,
5 type MobileThemeAppearance,
6 type MobileThemeVariables,
7} from "./mobileTheme";
48
59export function readDefaultMobileThemeVariables(
610 appearance: MobileThemeAppearance,
711): MobileThemeVariables {
8 const stylesheet = NodeFS.readFileSync(new URL("../../global.css", import.meta.url), "utf8");
12 const stylesheet = NodeFS.readFileSync(
13 new URL("../../generated-uniwind-themes.css", import.meta.url),
14 "utf8",
15 );
916 const variant = new RegExp(`@variant ${appearance} \\{([\\s\\S]*?)\\n \\}`, "u").exec(
1017 stylesheet,
1118 )?.[1];
12 if (variant === undefined) throw new Error(`Missing default ${appearance} theme in global.css.`);
19 if (variant === undefined) throw new Error(`Missing generated default ${appearance} theme.`);
1320
1421 return Object.fromEntries(
1522 Array.from(variant.matchAll(/(--color-[a-z0-9-]+):\s*([^;]+);/gu), ([, name, value]) => [
1623 name,
1724 (value ?? "").trim(),
18 ]),
25 ]).filter(([name]) =>
26 MOBILE_THEME_VARIABLE_NAMES.includes(name as (typeof MOBILE_THEME_VARIABLE_NAMES)[number]),
27 ),
1928 ) as MobileThemeVariables;
2029}
apps/mobile/src/lib/mobileTheme.test.ts+147-10
......@@ -1,12 +1,22 @@
11import { describe, expect, it } from "vite-plus/test";
2import { BUILT_IN_THEME_IDS, BUILT_IN_THEMES, T3_CHAT_THEME } from "@t3tools/shared/themePalettes";
2import {
3 BUILT_IN_THEME_IDS,
4 BUILT_IN_THEMES,
5 T3_CHAT_THEME,
6 T3_CODE_LIGHT_THEME_COLORS,
7 T3_CODE_DARK_THEME_COLORS,
8 MOBILE_THEME_IDS,
9 getThemeColorsForAppearance,
10} from "@t3tools/shared/themePalettes";
311import { readDefaultMobileThemeVariables } from "./mobileTheme.test-support";
12import { getMobileThemeRuntimeVariables } from "./mobileThemeVariables";
413
514import {
615 createMobileThemePairPatch,
716 createMobileThemeSelectionPatch,
817 createMobileThemeVariables,
918 DEFAULT_MOBILE_THEME_ID,
19 flattenThemeColor,
1020 getMobileThemePreviewColors,
1121 getMobileThemeVariables,
1222 normalizeMobileThemeId,
......@@ -66,12 +76,86 @@ describe("mobile themes", () => {
6676 }
6777 });
6878
69 it("preserves the existing mobile palette as the default", () => {
70 expect(readDefaultMobileThemeVariables("light")["--color-screen"]).toBe("#f2f2f7");
71 expect(readDefaultMobileThemeVariables("dark")["--color-screen"]).toBe("#0a0a0a");
72 expect(readDefaultMobileThemeVariables("light")["--color-user-bubble-skill-foreground"]).toBe(
73 "#2563eb",
74 );
79 it.each(MOBILE_THEME_IDS)("uses the web color roles for %s in both appearances", (themeId) => {
80 for (const appearance of ["light", "dark"] as const) {
81 const theme = BUILT_IN_THEMES.find((candidate) => candidate.id === themeId);
82 const colors = theme
83 ? getThemeColorsForAppearance(theme, appearance)!
84 : appearance === "dark"
85 ? T3_CODE_DARK_THEME_COLORS
86 : T3_CODE_LIGHT_THEME_COLORS;
87 const variables =
88 themeId === DEFAULT_MOBILE_THEME_ID
89 ? readDefaultMobileThemeVariables(appearance)
90 : getMobileThemeVariables(themeId, appearance);
91 expect(variables["--color-screen"]).toBe(themeColorToNativeColor(colors.canvas));
92 expect(variables["--color-thread-canvas"]).toBe(variables["--color-screen"]);
93 expect(variables["--color-drawer"]).toBe(themeColorToNativeColor(colors.sidebar));
94 expect(variables["--color-thread-hover"]).toBe(
95 themeColorToNativeColor(colors.sidebarRowHover),
96 );
97 expect(variables["--color-card"]).toBe(themeColorToNativeColor(colors.surface));
98 expect(variables["--color-composer-surface"]).toBe(
99 themeColorWithAlpha(
100 themeId === DEFAULT_MOBILE_THEME_ID
101 ? variables["--color-grouped-card"]
102 : themeColorToNativeColor(colors.surface),
103 appearance === "dark" ? 0.9 : 0.94,
104 ),
105 );
106 expect(variables["--color-thread-selected"]).toBe(
107 themeColorToNativeColor(colors.sidebarRowActive),
108 );
109 expect(variables["--color-thread-selected-foreground"]).toBe(
110 themeColorToNativeColor(colors.sidebarForeground),
111 );
112 expect(variables["--color-primary"]).toBe(themeColorToNativeColor(colors.messageAction));
113 if (themeId !== DEFAULT_MOBILE_THEME_ID) {
114 expect(variables["--color-user-bubble"]).toBe(
115 themeColorToNativeColor(colors.messageSurface),
116 );
117 }
118 expect(
119 contrastRatio(variables["--color-foreground"], variables["--color-screen"]),
120 ).toBeGreaterThanOrEqual(4.5);
121 for (const [foreground, surface] of [
122 ["--color-drawer-foreground", "--color-drawer"],
123 ["--color-drawer-foreground-muted", "--color-drawer"],
124 ["--color-drawer-foreground", "--color-thread-hover"],
125 ["--color-drawer-foreground-muted", "--color-thread-hover"],
126 ["--color-thread-selected-foreground-muted", "--color-thread-selected"],
127 ["--color-primary-foreground", "--color-primary"],
128 ["--color-primary-text", "--color-screen"],
129 ["--color-primary-text", "--color-card"],
130 ["--color-primary-text", "--color-card-alt"],
131 ["--color-primary-text", "--color-sheet-solid"],
132 ["--color-foreground", "--color-sheet-solid"],
133 ["--color-foreground-muted", "--color-sheet-solid"],
134 ["--color-primary-text", "--color-grouped-card"],
135 ["--color-foreground", "--color-grouped-card"],
136 ["--color-foreground-muted", "--color-grouped-card"],
137 ["--color-placeholder", "--color-grouped-card"],
138 ["--color-secondary-foreground", "--color-secondary"],
139 ["--color-user-bubble-foreground", "--color-user-bubble"],
140 ["--color-warning-foreground", "--color-warning"],
141 ["--color-danger-foreground", "--color-danger"],
142 ["--color-md-body", "--color-screen"],
143 ["--color-md-strong", "--color-screen"],
144 ["--color-md-link", "--color-screen"],
145 ["--color-md-code-text", "--color-md-code-bg"],
146 ] as const) {
147 expect(
148 contrastRatio(variables[foreground], variables[surface]),
149 `${appearance}: ${foreground} on ${surface}`,
150 ).toBeGreaterThanOrEqual(4.5);
151 }
152 expect(
153 contrastRatio(
154 variables["--color-thread-selected-foreground"],
155 variables["--color-thread-selected"],
156 ),
157 ).toBeGreaterThanOrEqual(4.5);
158 }
75159 });
76160
77161 it("applies palette overrides on top of the selected built-in theme", () => {
......@@ -83,6 +167,62 @@ describe("mobile themes", () => {
83167 expect(variables["--color-screen"]).toMatch(/^#/);
84168 });
85169
170 it.each(["light", "dark"] as const)(
171 "separates default settings groups from their %s background",
172 (appearance) => {
173 const variables = getMobileThemeVariables("t3-code", appearance);
174 expect(
175 contrastRatio(variables["--color-grouped-card"], variables["--color-sheet-solid"]),
176 ).toBeGreaterThanOrEqual(1.06);
177 expect(variables["--color-grouped-card"]).not.toBe(variables["--color-card"]);
178 for (const platform of ["ios", "android"]) {
179 const runtime = getMobileThemeRuntimeVariables("t3-code", appearance, platform);
180 const sidebar = flattenThemeColor(runtime["--color-drawer"], runtime["--color-screen"]);
181 const chrome = flattenThemeColor(
182 runtime[platform === "android" ? "--color-header" : "--color-drawer"],
183 runtime["--color-screen"],
184 );
185 expect(relativeLuminance(sidebar)).toBeLessThan(
186 relativeLuminance(runtime["--color-thread-canvas"]),
187 );
188 expect(contrastRatio(chrome, runtime["--color-screen"])).toBeGreaterThanOrEqual(1.06);
189 const foregroundRoles =
190 platform === "android"
191 ? (["--color-header-foreground", "--color-foreground-muted"] as const)
192 : (["--color-drawer-foreground", "--color-drawer-foreground-muted"] as const);
193 for (const role of foregroundRoles) {
194 expect(contrastRatio(runtime[role], chrome)).toBeGreaterThanOrEqual(4.5);
195 }
196 }
197 },
198 );
199
200 it.each(["light", "dark"] as const)(
201 "slightly strengthens default %s messages and separates fallback materials",
202 (appearance) => {
203 const variables = getMobileThemeVariables("t3-code", appearance);
204 const desktop =
205 appearance === "dark" ? T3_CODE_DARK_THEME_COLORS : T3_CODE_LIGHT_THEME_COLORS;
206 const bubbleContrast = contrastRatio(
207 variables["--color-user-bubble"],
208 variables["--color-screen"],
209 );
210 expect(bubbleContrast).toBeGreaterThan(contrastRatio(desktop.messageSurface, desktop.canvas));
211 expect(bubbleContrast).toBeLessThan(1.2);
212 for (const role of ["--color-composer-surface", "--color-glass-fallback"] as const) {
213 const surface = flattenThemeColor(variables[role], variables["--color-screen"]);
214 expect(contrastRatio(surface, variables["--color-screen"])).toBeGreaterThanOrEqual(1.06);
215 for (const foreground of [
216 "--color-foreground",
217 "--color-placeholder",
218 "--color-primary-text",
219 ] as const) {
220 expect(contrastRatio(variables[foreground], surface)).toBeGreaterThanOrEqual(4.5);
221 }
222 }
223 },
224 );
225
86226 it("uses the same preview roles and standard artwork as desktop", () => {
87227 expect(getMobileThemePreviewColors(DEFAULT_MOBILE_THEME_ID, "light")).toEqual({
88228 canvas: "#fcfcfc",
......@@ -153,7 +293,6 @@ describe("mobile themes", () => {
153293
154294 it("maps semantic palette roles onto every mobile color variable", () => {
155295 const variables = createMobileThemeVariables(T3_CHAT_THEME.colors, "light");
156 expect(Object.keys(variables)).toHaveLength(75);
157296 expect(variables["--color-sheet-solid"]).toBe(
158297 themeColorToNativeColor(T3_CHAT_THEME.colors.chrome),
159298 );
......@@ -219,8 +358,6 @@ describe("mobile themes", () => {
219358 }
220359 });
221360
222 // The default palette lives in global.css rather than BUILT_IN_THEMES, so the loops above
223 // never reached it; it kept an unreadable hardcoded bubble until this covered it.
224361 it("keeps the default user bubble readable in both appearances", () => {
225362 for (const appearance of ["light", "dark"] as const) {
226363 const variables = readDefaultMobileThemeVariables(appearance);
apps/mobile/src/lib/mobileTheme.ts+106-43
......@@ -1,10 +1,11 @@
11import {
22 BUILT_IN_THEMES,
33 T3_CHAT_THEME,
4 T3_CODE_LIGHT_THEME_COLORS,
5 T3_CODE_DARK_THEME_COLORS,
46 getThemeColorsForAppearance,
57 MOBILE_DEFAULT_THEME_ID,
68 MOBILE_THEME_IDS as SHARED_MOBILE_THEME_IDS,
7 type BuiltInThemeId,
89 type MobileThemeId as SharedMobileThemeId,
910 type ThemeAppearance,
1011 type ThemeColors,
......@@ -138,7 +139,7 @@ function withAlpha(color: string, alpha: number): string {
138139}
139140
140141function rgbChannels(color: string): readonly [number, number, number] | null {
141 const match = /^#([\da-f]{2})([\da-f]{2})([\da-f]{2})$/i.exec(color);
142 const match = /^#([\da-f]{2})([\da-f]{2})([\da-f]{2})(?:[\da-f]{2})?$/i.exec(color);
142143 if (!match) return null;
143144 const [, red = "0", green = "0", blue = "0"] = match;
144145 return [Number.parseInt(red, 16), Number.parseInt(green, 16), Number.parseInt(blue, 16)];
......@@ -150,6 +151,14 @@ function rgbChannels(color: string): readonly [number, number, number] | null {
150151 * colour asked for — so a translucent role like `--color-border` has to be flattened first.
151152 */
152153export function flattenThemeColor(color: string, surface: string): string {
154 const alphaHex = /^#([\da-f]{6})([\da-f]{2})$/i.exec(color);
155 if (alphaHex) {
156 const channels = rgbChannels(color)!;
157 return flattenThemeColor(
158 `rgba(${channels[0]}, ${channels[1]}, ${channels[2]}, ${Number.parseInt(alphaHex[2]!, 16) / 255})`,
159 surface,
160 );
161 }
153162 const match = /^rgba?\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)(?:[,\s/]+([\d.]+))?\s*\)$/i.exec(
154163 color.trim(),
155164 );
......@@ -182,22 +191,21 @@ function contrastRatio(
182191 );
183192}
184193
185/** Preserve the theme's action hue while making it readable as skill text on a message bubble. */
186function readableMessageAccent(accent: string, surface: string): string {
194/** Preserve the color's hue while giving text 4.5:1 contrast on every supplied surface. */
195function readableTextColor(accent: string, surface: string | ReadonlyArray<string>): string {
187196 const accentChannels = rgbChannels(accent);
188 const surfaceChannels = rgbChannels(surface);
189 if (
190 !accentChannels ||
191 !surfaceChannels ||
192 contrastRatio(accentChannels, surfaceChannels) >= 4.5
193 ) {
197 const surfaceChannels = (typeof surface === "string" ? [surface] : surface)
198 .map(rgbChannels)
199 .filter((channels) => channels !== null);
200 const minimumContrast = (channels: readonly [number, number, number]) =>
201 Math.min(...surfaceChannels.map((surface) => contrastRatio(channels, surface)));
202 if (!accentChannels || surfaceChannels.length === 0 || minimumContrast(accentChannels) >= 4.5) {
194203 return accent;
195204 }
196205
197206 const black = [0, 0, 0] as const;
198207 const white = [255, 255, 255] as const;
199 const target =
200 contrastRatio(black, surfaceChannels) >= contrastRatio(white, surfaceChannels) ? black : white;
208 const target = minimumContrast(black) >= minimumContrast(white) ? black : white;
201209 let readable: readonly [number, number, number] = target;
202210 let lowerAmount = 0;
203211 let upperAmount = 1;
......@@ -208,7 +216,7 @@ function readableMessageAccent(accent: string, surface: string): string {
208216 Math.round(accentChannels[1] + (target[1] - accentChannels[1]) * amount),
209217 Math.round(accentChannels[2] + (target[2] - accentChannels[2]) * amount),
210218 ];
211 if (contrastRatio(candidate, surfaceChannels) >= 4.5) {
219 if (minimumContrast(candidate) >= 4.5) {
212220 readable = candidate;
213221 upperAmount = amount;
214222 } else {
......@@ -227,30 +235,46 @@ export function themeColorWithAlpha(color: string, alpha: number): string {
227235 return rgb ? `rgba(${rgb[1]}, ${rgb[2]}, ${rgb[3]}, ${alpha})` : color;
228236}
229237
230export function createMobileThemeVariables(colors: ThemeColors, appearance: MobileThemeAppearance) {
238export function createMobileThemeVariables(
239 colors: ThemeColors,
240 appearance: MobileThemeAppearance,
241 groupedCardColor = colors.surface,
242) {
231243 const c = nativeColors(colors);
244 const groupedCard = themeColorToNativeColor(groupedCardColor);
245 const textSurfaces = [c.canvas, c.surface, c.surfaceRaised, c.chrome, groupedCard];
232246 return {
233247 "--color-screen": c.canvas,
234248 "--color-sheet": withAlpha(c.chrome, 0.98),
235249 "--color-sheet-solid": c.chrome,
236 "--color-card": c.surfaceRaised,
237 "--color-card-alt": c.surface,
238 "--color-card-translucent": withAlpha(c.surfaceRaised, 0.8),
239 "--color-thread-canvas": c.surface,
240 "--color-thread-selected": c.surfaceRaised,
241 "--color-thread-selected-foreground": c.text,
242 "--color-thread-selected-foreground-muted": c.textMuted,
243 "--color-composer-panel": themeColorWithAlpha(c.surface, appearance === "dark" ? 0.92 : 0.88),
250 "--color-card": c.surface,
251 "--color-grouped-card": groupedCard,
252 "--color-card-alt": c.surfaceRaised,
253 "--color-card-translucent": withAlpha(c.surface, 0.8),
254 "--color-thread-canvas": c.canvas,
255 "--color-thread-selected": c.sidebarRowActive,
256 "--color-thread-selected-foreground": c.sidebarForeground,
257 "--color-thread-selected-foreground-muted": readableTextColor(
258 c.sidebarMutedForeground,
259 c.sidebarRowActive,
260 ),
261 "--color-thread-hover": c.sidebarRowHover,
262 "--color-row-hover": c.toolbarControlHover,
263 "--color-composer-panel": themeColorWithAlpha(c.canvas, appearance === "dark" ? 0.92 : 0.88),
244264 "--color-composer-surface": themeColorWithAlpha(
245 c.surfaceRaised,
265 groupedCard,
246266 appearance === "dark" ? 0.9 : 0.94,
247267 ),
248 "--color-composer-border": themeColorWithAlpha(c.border, appearance === "dark" ? 0.46 : 0.54),
268 "--color-composer-border": themeColorWithAlpha(
269 c.border,
270 groupedCard === c.surface ? (appearance === "dark" ? 0.46 : 0.54) : 0.8,
271 ),
249272 "--color-foreground": c.text,
250 "--color-foreground-secondary": c.textMuted,
251 "--color-foreground-muted": c.mutedForeground,
273 "--color-foreground-secondary": readableTextColor(c.textMuted, textSurfaces),
274 "--color-foreground-muted": readableTextColor(c.mutedForeground, textSurfaces),
252275 "--color-foreground-tertiary": c.secondaryLabel,
253276 "--color-border": c.border,
277 "--color-focus": c.focus,
254278 "--color-border-subtle": withAlpha(c.border, 0.7),
255279 "--color-separator": withAlpha(c.border, 0.55),
256280 "--color-subtle": c.muted,
......@@ -258,14 +282,15 @@ export function createMobileThemeVariables(colors: ThemeColors, appearance: Mobi
258282 "--color-inline-skill-background": c.accentSurface,
259283 "--color-inline-skill-border": withAlpha(c.accent, 0.42),
260284 "--color-inline-skill-foreground": c.accentSurfaceForeground,
261 "--color-primary": c.accent,
262 "--color-primary-foreground": c.accentForeground,
285 "--color-primary": c.messageAction,
286 "--color-primary-foreground": c.messageActionForeground,
287 "--color-primary-text": readableTextColor(c.messageAction, textSurfaces),
263288 "--color-primary-shadow": "#000000",
264289 "--color-secondary": c.secondary,
265290 "--color-secondary-foreground": c.secondaryForeground,
266291 "--color-secondary-border": c.border,
267 "--color-switch-active-track": c.accent,
268 "--color-switch-active-thumb": c.accentForeground,
292 "--color-switch-active-track": c.messageAction,
293 "--color-switch-active-thumb": c.messageActionForeground,
269294 "--color-switch-inactive-track": c.secondary,
270295 "--color-switch-inactive-thumb": c.mutedForeground,
271296 "--color-warning": c.warningSurface,
......@@ -274,21 +299,25 @@ export function createMobileThemeVariables(colors: ThemeColors, appearance: Mobi
274299 "--color-danger": c.errorSurface,
275300 "--color-danger-border": withAlpha(c.error, 0.32),
276301 "--color-danger-foreground": c.errorForeground,
277 "--color-input": c.surfaceRaised,
302 "--color-update": c.updateSurface,
303 "--color-update-foreground": c.updateForeground,
304 "--color-input": c.surface,
278305 "--color-input-border": c.input,
279306 "--color-sidebar-search": c.sidebarControlSurface,
280 "--color-placeholder": c.placeholder,
307 "--color-placeholder": readableTextColor(c.placeholder, textSurfaces),
281308 "--color-icon": c.text,
282309 "--color-icon-muted": c.iconMuted,
283310 "--color-icon-subtle": c.secondaryLabel,
284311 "--color-header": withAlpha(c.toolbar, 0.97),
312 "--color-header-foreground": c.toolbarForeground,
285313 "--color-header-border": c.toolbarBorder,
286314 "--color-glass-surface": withAlpha(c.surfaceOverlay, 0.74),
315 "--color-glass-fallback": themeColorWithAlpha(groupedCard, appearance === "dark" ? 0.9 : 0.94),
287316 "--color-glass-tint": withAlpha(c.surfaceOverlay, 0.22),
288317 "--color-status-bar": c.canvas,
289318 "--color-md-body": c.text,
290 "--color-md-strong": c.toolbarForeground,
291 "--color-md-link": c.accent,
319 "--color-md-strong": c.text,
320 "--color-md-link": readableTextColor(c.messageAction, c.canvas),
292321 "--color-md-blockquote-border": c.border,
293322 "--color-md-blockquote-bg": c.muted,
294323 "--color-md-code-bg": c.codeBackground,
......@@ -301,12 +330,15 @@ export function createMobileThemeVariables(colors: ThemeColors, appearance: Mobi
301330 "--color-user-bubble": c.messageSurface,
302331 "--color-user-bubble-foreground": c.messageForeground,
303332 "--color-user-bubble-foreground-muted": withAlpha(c.messageForeground, 0.78),
304 "--color-user-bubble-skill-foreground": readableMessageAccent(
305 c.messageAction,
306 c.messageSurface,
307 ),
333 "--color-user-bubble-skill-foreground": readableTextColor(c.messageAction, c.messageSurface),
308334 "--color-backdrop": withAlpha("#000000", appearance === "dark" ? 0.48 : 0.22),
309 "--color-drawer": withAlpha(c.sidebar, 0.99),
335 "--color-drawer": c.sidebar,
336 "--color-drawer-foreground": c.sidebarForeground,
337 "--color-drawer-foreground-muted": readableTextColor(c.sidebarMutedForeground, [
338 c.sidebar,
339 c.sidebarRowHover,
340 ]),
341 "--color-drawer-border": c.sidebarBorder,
310342 "--color-drawer-shadow": withAlpha("#000000", appearance === "dark" ? 0.32 : 0.12),
311343 "--color-dot-separator": withAlpha(c.textMuted, 0.35),
312344 "--color-wordmark": c.text,
......@@ -318,14 +350,45 @@ export const MOBILE_THEME_VARIABLE_NAMES = Object.keys(
318350 createMobileThemeVariables(T3_CHAT_THEME.colors, "light"),
319351) as ReadonlyArray<MobileThemeVariable>;
320352
353export function getMobileThemeColors(
354 themeId: SharedMobileThemeId,
355 appearance: MobileThemeAppearance,
356): ThemeColors {
357 if (themeId === DEFAULT_MOBILE_THEME_ID) {
358 return appearance === "dark" ? T3_CODE_DARK_THEME_COLORS : T3_CODE_LIGHT_THEME_COLORS;
359 }
360 const theme = BUILT_IN_THEMES.find((candidate) => candidate.id === themeId) ?? T3_CHAT_THEME;
361 return getThemeColorsForAppearance(theme, appearance) ?? theme.colors;
362}
363
321364export function getMobileThemeVariables(
322 themeId: BuiltInThemeId,
365 themeId: SharedMobileThemeId,
323366 appearance: MobileThemeAppearance,
324367 overrides: Partial<MobileThemeVariables> | null = null,
325368): MobileThemeVariables {
326 const theme = BUILT_IN_THEMES.find((candidate) => candidate.id === themeId) ?? T3_CHAT_THEME;
327 const colors = getThemeColorsForAppearance(theme, appearance) ?? theme.colors;
328 const baseVariables = createMobileThemeVariables(colors, appearance);
369 const colors = getMobileThemeColors(themeId, appearance);
370 // Mobile settings groups and fallback materials use tonal fills where desktop
371 // uses outlined cards. Regular cards retain their shared desktop surface.
372 const groupedCard =
373 themeId === DEFAULT_MOBILE_THEME_ID
374 ? appearance === "light"
375 ? colors.toolbarControlHover
376 : colors.sidebarRowActive
377 : colors.surface;
378 const mobileColors =
379 themeId === DEFAULT_MOBILE_THEME_ID
380 ? {
381 ...colors,
382 messageSurface: flattenThemeColor(
383 themeColorWithAlpha(
384 appearance === "dark" ? colors.sidebarRowActive : colors.border,
385 0.3,
386 ),
387 colors.messageSurface,
388 ),
389 }
390 : colors;
391 const baseVariables = createMobileThemeVariables(mobileColors, appearance, groupedCard);
329392
330393 // The complete base record guarantees that optional overrides cannot leave a token undefined.
331394 return overrides ? ({ ...baseVariables, ...overrides } as MobileThemeVariables) : baseVariables;
apps/mobile/src/lib/mobileThemeVariables.test.ts+56-13
......@@ -5,11 +5,11 @@ import { getMobileThemeVariables, MOBILE_THEME_IDS, themeColorWithAlpha } from "
55import { getMobileThemeRuntimeVariables } from "./mobileThemeVariables";
66
77describe("mobile theme runtime variables", () => {
8 it("derives the standard runtime palette from global.css", () => {
9 expect(getMobileThemeRuntimeVariables("t3-code", "light", "ios")).toEqual(
8 it("matches the standard base palette to the generated stylesheet", () => {
9 expect(getMobileThemeRuntimeVariables("t3-code", "light", "web")).toEqual(
1010 readDefaultMobileThemeVariables("light"),
1111 );
12 expect(getMobileThemeRuntimeVariables("t3-code", "dark", "ios")).toEqual(
12 expect(getMobileThemeRuntimeVariables("t3-code", "dark", "web")).toEqual(
1313 readDefaultMobileThemeVariables("dark"),
1414 );
1515 });
......@@ -27,18 +27,22 @@ describe("mobile theme runtime variables", () => {
2727 "keeps %s colors on Android with an opaque Material frame",
2828 (themeId) => {
2929 for (const appearance of ["light", "dark"] as const) {
30 const ios = getMobileThemeRuntimeVariables(themeId, appearance, "ios");
30 const base = getMobileThemeVariables(
31 themeId === "material-you" ? "t3-code" : themeId,
32 appearance,
33 );
3134 const android = getMobileThemeRuntimeVariables(themeId, appearance, "android");
3235 expect(android).toEqual({
33 ...ios,
36 ...base,
3437 "--color-header": themeColorWithAlpha(
35 ios[
38 base[
3639 themeId === "t3-code" || themeId === "material-you"
37 ? "--color-card"
40 ? "--color-row-hover"
3841 : "--color-drawer"
3942 ],
4043 1,
4144 ),
45 "--color-header-foreground": base["--color-drawer-foreground"],
4246 });
4347 expect(android["--color-header"]).toMatch(/^rgba\(\d+, \d+, \d+, 1\)$/);
4448 }
......@@ -46,13 +50,52 @@ describe("mobile theme runtime variables", () => {
4650 );
4751
4852 it.each(["t3-code", "material-you"] as const)(
49 "keeps the %s default dark frame distinct from the rounded settings body",
53 "keeps the %s default Material frame distinct in both appearances",
54 (themeId) => {
55 for (const appearance of ["light", "dark"] as const) {
56 const variables = getMobileThemeRuntimeVariables(themeId, appearance, "android");
57 expect(variables["--color-header"]).toBe(
58 appearance === "light" ? "rgba(244, 244, 245, 1)" : "rgba(20, 20, 20, 1)",
59 );
60 for (const pane of ["--color-screen", "--color-sheet-solid", "--color-drawer"] as const) {
61 expect(variables["--color-header"]).not.toBe(themeColorWithAlpha(variables[pane], 1));
62 }
63 }
64 },
65 );
66
67 it.each(["t3-code", "material-you"] as const)(
68 "adapts %s iPad chrome without reversing the dark desktop hierarchy",
5069 (themeId) => {
51 const variables = getMobileThemeRuntimeVariables(themeId, "dark", "android");
52 expect(variables["--color-header"]).toBe("rgba(23, 23, 23, 1)");
53 expect(variables["--color-header"]).not.toBe(
54 themeColorWithAlpha(variables["--color-sheet-solid"], 1),
55 );
70 for (const appearance of ["light", "dark"] as const) {
71 const ios = getMobileThemeRuntimeVariables(themeId, appearance, "ios");
72 const android = getMobileThemeRuntimeVariables(themeId, appearance, "android");
73 if (appearance === "light") {
74 expect(ios["--color-drawer"]).toBe(android["--color-header"]);
75 expect(ios["--color-header"]).toBe(android["--color-header"]);
76 expect(ios["--color-header-foreground"]).toBe(ios["--color-drawer-foreground"]);
77 } else {
78 expect(ios).toEqual(getMobileThemeVariables("t3-code", appearance));
79 expect(ios["--color-drawer"]).toBe(android["--color-drawer"]);
80 expect(ios["--color-drawer"]).toBe("#000000");
81 expect(ios["--color-thread-canvas"]).toBe("#0a0a0a");
82 }
83 expect(themeColorWithAlpha(ios["--color-thread-hover"], 1)).not.toBe(
84 themeColorWithAlpha(ios["--color-drawer"], 1),
85 );
86 expect(themeColorWithAlpha(ios["--color-thread-hover"], 1)).not.toBe(
87 themeColorWithAlpha(ios["--color-thread-selected"], 1),
88 );
89 for (const role of [
90 "--color-screen",
91 "--color-thread-canvas",
92 "--color-card",
93 "--color-composer-surface",
94 "--color-grouped-card",
95 ] as const) {
96 expect(ios[role]).toBe(android[role]);
97 }
98 }
5699 },
57100 );
58101});
apps/mobile/src/lib/mobileThemeVariables.ts+20-9
......@@ -14,8 +14,8 @@ const defaults = defaultThemeVariables as Readonly<
1414
1515/**
1616 * Complete palette for native and third-party APIs that cannot consume a
17 * Uniwind className. The standard palette is generated from global.css; custom
18 * palettes share the same source that generates their registered CSS themes.
17 * Uniwind className. Every palette shares the source that generates its
18 * registered CSS theme.
1919 */
2020export function getMobileThemeRuntimeVariables(
2121 themeId: MobileThemeId,
......@@ -26,16 +26,27 @@ export function getMobileThemeRuntimeVariables(
2626 const variables = usesDefaultPalette
2727 ? defaults[appearance]
2828 : getMobileThemeVariables(themeId, appearance);
29 // Android's frame surrounds the sidebar and chat panes. Light iPad sidebars
30 // reuse that stronger tonal fill; dark sidebars retain the shared black pane
31 // beneath the near-black chat canvas. System colors replace these roles later.
32 const frame = themeColorWithAlpha(
33 variables[usesDefaultPalette ? "--color-row-hover" : "--color-drawer"],
34 1,
35 );
36 if (platform === "ios" && usesDefaultPalette && appearance === "light") {
37 return {
38 ...variables,
39 "--color-header": frame,
40 "--color-header-foreground": variables["--color-drawer-foreground"],
41 "--color-drawer": frame,
42 "--color-drawer-foreground-muted": variables["--color-foreground-muted"],
43 };
44 }
2945 if (platform !== "android") return variables;
3046
31 // Rounded panes share one opaque frame. The default dark drawer matches the
32 // settings body, so use its card tone to keep the rounded edge visible.
33 // System colors replace this with their own surfaceContainerHigh afterwards.
3447 return {
3548 ...variables,
36 "--color-header": themeColorWithAlpha(
37 variables[usesDefaultPalette ? "--color-card" : "--color-drawer"],
38 1,
39 ),
49 "--color-header": frame,
50 "--color-header-foreground": variables["--color-drawer-foreground"],
4051 };
4152}
apps/mobile/src/lib/nativeComposerTheme.test.ts created+32
......@@ -0,0 +1,32 @@
1import { describe, expect, it } from "vite-plus/test";
2import { MOBILE_THEME_IDS } from "@t3tools/shared/themePalettes";
3
4import { getMobileThemeVariables } from "./mobileTheme";
5import { createNativeComposerTheme } from "./nativeComposerTheme";
6
7describe("native composer colors", () => {
8 it.each(MOBILE_THEME_IDS)("delivers parsable colors for %s in both appearances", (themeId) => {
9 for (const appearance of ["light", "dark"] as const) {
10 const variables = getMobileThemeVariables(themeId, appearance);
11 const theme = createNativeComposerTheme(variables);
12 for (const color of Object.values(theme)) expect(color).toMatch(/^#[\da-f]{6}$/i);
13 expect(theme.text).toBe(variables["--color-foreground"]);
14 expect(theme.skillBorder).not.toBe(theme.skillBackground);
15 }
16 });
17
18 it("flattens Material You alpha colors over the composer rather than falling back to defaults", () => {
19 const theme = createNativeComposerTheme({
20 ...getMobileThemeVariables("t3-code", "light"),
21 "--color-screen": "#ffffffff",
22 "--color-composer-surface": "#ffffffff",
23 "--color-subtle": "#0000000d",
24 "--color-placeholder": "#0000009e",
25 "--color-inline-skill-background": "#ffffffff",
26 "--color-inline-skill-border": "#00000080",
27 });
28 expect(theme.chipBackground).toBe("#f2f2f2");
29 expect(theme.placeholder).toBe("#616161");
30 expect(theme.skillBorder).toBe("#7f7f7f");
31 });
32});
apps/mobile/src/lib/nativeComposerTheme.ts created+19
......@@ -0,0 +1,19 @@
1import { flattenThemeColor, type MobileThemeVariables } from "./mobileTheme";
2
3/** Native chip parsers need opaque hex instead of CSS rgba or platform-specific alpha order. */
4export function createNativeComposerTheme(theme: MobileThemeVariables) {
5 const surface = flattenThemeColor(theme["--color-composer-surface"], theme["--color-screen"]);
6 const chipBackground = flattenThemeColor(theme["--color-subtle"], surface);
7 const skillBackground = flattenThemeColor(theme["--color-inline-skill-background"], surface);
8 return {
9 text: flattenThemeColor(theme["--color-foreground"], surface),
10 placeholder: flattenThemeColor(theme["--color-placeholder"], surface),
11 chipBackground,
12 chipBorder: flattenThemeColor(theme["--color-border"], chipBackground),
13 chipText: flattenThemeColor(theme["--color-foreground"], chipBackground),
14 skillBackground,
15 skillBorder: flattenThemeColor(theme["--color-inline-skill-border"], skillBackground),
16 skillText: flattenThemeColor(theme["--color-inline-skill-foreground"], skillBackground),
17 fileTint: flattenThemeColor(theme["--color-icon-muted"], chipBackground),
18 };
19}
apps/mobile/src/lib/useMobileNavigationTheme.ts+10-7
......@@ -10,7 +10,7 @@ import { useUniwindTheme } from "./useUniwindTheme";
1010 * preferences provider applies the registered Uniwind theme first, then
1111 * publishes this matching navigation palette through React.
1212 */
13export function useMobileNavigationTheme(): Theme {
13export function useMobileNavigationTheme(surface: "screen" | "sidebar" = "screen"): Theme {
1414 const { themeAppearance: appearance } = useAppearancePreferences();
1515 const variables = useUniwindTheme();
1616 return useMemo(() => {
......@@ -19,13 +19,16 @@ export function useMobileNavigationTheme(): Theme {
1919 ...base,
2020 colors: {
2121 ...base.colors,
22 primary: variables["--color-primary"],
23 background: variables["--color-screen"],
24 card: variables["--color-sheet-solid"],
25 text: variables["--color-foreground"],
26 border: variables["--color-header-border"],
22 primary: variables["--color-primary-text"],
23 background: variables[surface === "sidebar" ? "--color-drawer" : "--color-screen"],
24 card: variables[surface === "sidebar" ? "--color-drawer" : "--color-sheet-solid"],
25 text: variables[
26 surface === "sidebar" ? "--color-drawer-foreground" : "--color-header-foreground"
27 ],
28 border:
29 variables[surface === "sidebar" ? "--color-drawer-border" : "--color-header-border"],
2730 notification: variables["--color-danger-foreground"],
2831 },
2932 };
30 }, [appearance, variables]);
33 }, [appearance, surface, variables]);
3134}
apps/mobile/src/native/SelectableMarkdownText.android.tsx+2-2
......@@ -24,8 +24,8 @@ export function hasNativeSelectableMarkdownText(): boolean {
2424
2525export function SelectableMarkdownText(props: MobileSelectableMarkdownTextProps) {
2626 const theme = useUniwindTheme();
27 const selectionColor = themeColorWithAlpha(theme["--color-primary"], 0.32);
28 const selectionHandleColor = theme["--color-primary"];
27 const selectionColor = themeColorWithAlpha(theme["--color-focus"], 0.32);
28 const selectionHandleColor = theme["--color-focus"];
2929 const textStyle = useMemo(
3030 () => ({ selectionColor, selectionHandleColor, ...props.textStyle }),
3131 [props.textStyle, selectionColor, selectionHandleColor],
apps/mobile/src/native/T3ComposerEditor.ios.tsx+2-13
......@@ -22,7 +22,7 @@ import {
2222} from "@t3tools/mobile-markdown-text/markdown";
2323import { resolveMarkdownFileIcon } from "@t3tools/mobile-markdown-text/links";
2424import { useUniwindTheme } from "../lib/useUniwindTheme";
25import { flattenThemeColor } from "../lib/mobileTheme";
25import { createNativeComposerTheme } from "../lib/nativeComposerTheme";
2626import { useFontFamily } from "../lib/useFontFamily";
2727import { useScaledTextRole } from "../features/settings/appearance/useScaledTextRole";
2828import {
......@@ -257,18 +257,7 @@ export function ComposerEditor({
257257 },
258258 [],
259259 );
260 const themeJson = JSON.stringify({
261 text: theme["--color-foreground"],
262 placeholder: theme["--color-placeholder"],
263 chipBackground: theme["--color-subtle"],
264 // Native chip drawing parses opaque hex only, and this role is translucent.
265 chipBorder: flattenThemeColor(theme["--color-border"], theme["--color-user-bubble"]),
266 chipText: theme["--color-foreground"],
267 skillBackground: theme["--color-inline-skill-background"],
268 skillBorder: theme["--color-inline-skill-border"],
269 skillText: theme["--color-inline-skill-foreground"],
270 fileTint: theme["--color-icon-muted"],
271 });
260 const themeJson = JSON.stringify(createNativeComposerTheme(theme));
272261 const resolvedTextStyle = StyleSheet.flatten(textStyle) ?? {};
273262 return (
274263 <NativeView
apps/mobile/src/native/T3ComposerEditor.native.tsx+3-12
......@@ -27,7 +27,7 @@ import { useNativePaste } from "../lib/useNativePaste";
2727import { useFontFamily } from "../lib/useFontFamily";
2828import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider";
2929import { useUniwindTheme } from "../lib/useUniwindTheme";
30import { flattenThemeColor } from "../lib/mobileTheme";
30import { createNativeComposerTheme } from "../lib/nativeComposerTheme";
3131import {
3232 acknowledgeComposerNativeEvent,
3333 assumeComposerControlledState,
......@@ -259,17 +259,8 @@ export function ComposerEditor({
259259 );
260260 const { systemColorsActive } = useAppearancePreferences();
261261 const themeJson = JSON.stringify({
262 selection: Platform.OS === "android" || systemColorsActive ? theme["--color-primary"] : null,
263 text: theme["--color-foreground"],
264 placeholder: theme["--color-placeholder"],
265 chipBackground: theme["--color-subtle"],
266 // Native chip drawing parses opaque hex only, and this role is translucent.
267 chipBorder: flattenThemeColor(theme["--color-border"], theme["--color-user-bubble"]),
268 chipText: theme["--color-foreground"],
269 skillBackground: theme["--color-inline-skill-background"],
270 skillBorder: theme["--color-inline-skill-border"],
271 skillText: theme["--color-inline-skill-foreground"],
272 fileTint: theme["--color-icon-muted"],
262 ...createNativeComposerTheme(theme),
263 selection: Platform.OS === "android" || systemColorsActive ? theme["--color-focus"] : null,
273264 });
274265 const resolvedTextStyle = StyleSheet.flatten(textStyle) ?? {};
275266 const regularFontFamily = useFontFamily("regular");
apps/web/src/themePalette.test.ts+15
......@@ -103,6 +103,21 @@ describe("theme files", () => {
103103 });
104104 });
105105
106 it("keeps the stock sidebar and chat on distinct surfaces in both appearances", () => {
107 expectThemeColors(getStandardThemeColors("light"), {
108 canvas: "#fcfcfc",
109 sidebar: "#fafafa",
110 sidebarRowActive: "#ffffff",
111 messageSurface: "#f4f4f5",
112 });
113 expectThemeColors(getStandardThemeColors("dark"), {
114 canvas: "#0a0a0a",
115 sidebar: "#000000",
116 sidebarRowActive: "#1a1b1b",
117 messageSurface: "#141414",
118 });
119 });
120
106121 it("derives readable, distinctive vivid palettes from exact seeds", () => {
107122 const seeds: ReadonlyArray<["light" | "dark", string, string]> = [
108123 ["light", "#f4f9f2", "#1d8a4e"],
apps/web/src/themePalette.ts+2-127
......@@ -9,6 +9,8 @@ import {
99 IRIS_THEME,
1010 OCEAN_THEME,
1111 T3_CHAT_THEME,
12 T3_CODE_LIGHT_THEME_COLORS,
13 T3_CODE_DARK_THEME_COLORS,
1214 RESERVED_THEME_IDS,
1315 THEME_COLOR_ROLES,
1416 type ThemeAppearance,
......@@ -326,133 +328,6 @@ function legacyThemeMode(theme: ThemePreference): ThemeAppearance | null {
326328 return theme === LEGACY_T3_CHAT_DARK_THEME_ID ? "dark" : null;
327329}
328330
329/**
330 * The palette T3 Code wears with no theme installed, captured from the app's
331 * stock tokens (index.css) so a draft seeded from the default look paints the
332 * pixels the user is already seeing. Alpha-bearing tokens are flattened over
333 * their real backdrops (canvas, or the sidebar for its rows) because theme
334 * colors are stored as opaque OKLCH tokens.
335 */
336const T3_CODE_LIGHT_THEME_COLORS: ThemeColors = {
337 canvas: "#fcfcfc",
338 chrome: "#fcfcfc",
339 toolbar: "#fcfcfc",
340 toolbarForeground: "#27272a",
341 toolbarBorder: "#e4e4e7",
342 toolbarControl: "#ffffff",
343 toolbarControlForeground: "#27272a",
344 toolbarControlHover: "#f4f4f5",
345 surface: "#ffffff",
346 surfaceRaised: "#fcfcfc",
347 surfaceOverlay: "#ffffff",
348 text: "#27272a",
349 textMuted: "#71717b",
350 border: "#e4e4e7",
351 input: "#d4d4d8",
352 focus: "#1b4ed8",
353 accent: "#1b4ed8",
354 accentForeground: "#ffffff",
355 secondary: "#fafafa",
356 secondaryForeground: "#27272a",
357 muted: "#fafafa",
358 mutedForeground: "#71717b",
359 placeholder: "#71717b",
360 secondaryLabel: "#71717b",
361 iconMuted: "#71717b",
362 error: "#fb2c36",
363 errorForeground: "#c10007",
364 errorSurface: "#fcebec",
365 warning: "#fe9a00",
366 warningForeground: "#bb4d00",
367 warningSurface: "#fcf4e8",
368 update: "#1b4ed8",
369 updateForeground: "#1b4ed8",
370 updateSurface: "#e0e6f7",
371 accentSurface: "#f4f4f5",
372 accentSurfaceForeground: "#18181b",
373 messageSurface: "#f4f4f5",
374 messageForeground: "#27272a",
375 messageAction: "#1b4ed8",
376 messageActionForeground: "#ffffff",
377 messageActionHover: "#3160db",
378 codeBackground: "#ffffff",
379 codeForeground: "#27272a",
380 sidebar: "#fafafa",
381 sidebarForeground: "#27272a",
382 sidebarMutedForeground: "#71717b",
383 sidebarControlSurface: "#f4f4f5",
384 sidebarRowHover: "#fcfcfc",
385 sidebarRowActive: "#ffffff",
386 sidebarRowSelected: "#ffffff",
387 sidebarBorder: "#e4e4e7",
388 terminalBackground: "#fcfcfc",
389 terminalForeground: "#27272a",
390 terminalCursor: "#26384e",
391 terminalSelection: "#d0d6dd",
392 terminalScrollbar: "#d6d6d6",
393 terminalScrollbarHover: "#bdbdbd",
394};
395
396const T3_CODE_DARK_THEME_COLORS: ThemeColors = {
397 canvas: "#0a0a0a",
398 chrome: "#0a0a0a",
399 toolbar: "#0a0a0a",
400 toolbarForeground: "#f5f5f5",
401 toolbarBorder: "#191919",
402 toolbarControl: "#111111",
403 toolbarControlForeground: "#f5f5f5",
404 toolbarControlHover: "#141414",
405 surface: "#111111",
406 surfaceRaised: "#111111",
407 surfaceOverlay: "#111111",
408 text: "#f5f5f5",
409 textMuted: "#818181",
410 border: "#191919",
411 input: "#1e1e1e",
412 focus: "#346bf1",
413 accent: "#346bf1",
414 accentForeground: "#ffffff",
415 secondary: "#111111",
416 secondaryForeground: "#f5f5f5",
417 muted: "#111111",
418 mutedForeground: "#818181",
419 placeholder: "#818181",
420 secondaryLabel: "#818181",
421 iconMuted: "#818181",
422 error: "#fb414a",
423 errorForeground: "#ff6467",
424 errorSurface: "#301214",
425 warning: "#fe9a00",
426 warningForeground: "#ffb900",
427 warningSurface: "#312108",
428 update: "#346bf1",
429 updateForeground: "#51a2ff",
430 updateSurface: "#121b34",
431 accentSurface: "#141414",
432 accentSurfaceForeground: "#f5f5f5",
433 messageSurface: "#141414",
434 messageForeground: "#f5f5f5",
435 messageAction: "#346bf1",
436 messageActionForeground: "#ffffff",
437 messageActionHover: "#3061d9",
438 codeBackground: "#111111",
439 codeForeground: "#f5f5f5",
440 sidebar: "#000000",
441 sidebarForeground: "#f1f3f7",
442 sidebarMutedForeground: "#a3a3a3",
443 sidebarControlSurface: "#0a0a0a",
444 sidebarRowHover: "#131313",
445 sidebarRowActive: "#1a1b1b",
446 sidebarRowSelected: "#111111",
447 sidebarBorder: "#141414",
448 terminalBackground: "#0a0a0a",
449 terminalForeground: "#f5f5f5",
450 terminalCursor: "#b4cbff",
451 terminalSelection: "#343a47",
452 terminalScrollbar: "#222222",
453 terminalScrollbarHover: "#363636",
454};
455
456331/**
457332 * The standard T3 Code look as a theme palette, for seeding a new theme when
458333 * no theme is installed. Distinct from {@link getDefaultThemeColors}, which
packages/shared/src/themePalettes.ts+128-1
......@@ -1,6 +1,6 @@
11export const BUILT_IN_THEME_IDS = ["t3-chat", "grove", "ocean", "ember", "iris"] as const;
22
3/** The mobile app's own hand-tuned palette, which is not part of the built-in library. */
3/** The standard T3 Code palette, kept separate from the optional built-in theme library. */
44export const MOBILE_DEFAULT_THEME_ID = "t3-code";
55
66/**
......@@ -121,6 +121,133 @@ export type ThemeDefinition = Readonly<{
121121 managed?: boolean;
122122}>;
123123
124/**
125 * The palette T3 Code wears with no theme installed, captured from the app's
126 * stock tokens (index.css) so a draft seeded from the default look paints the
127 * pixels the user is already seeing. Alpha-bearing tokens are flattened over
128 * their real backdrops (canvas, or the sidebar for its rows) because theme
129 * colors are stored as opaque OKLCH tokens.
130 */
131export const T3_CODE_LIGHT_THEME_COLORS: ThemeColors = {
132 canvas: "#fcfcfc",
133 chrome: "#fcfcfc",
134 toolbar: "#fcfcfc",
135 toolbarForeground: "#27272a",
136 toolbarBorder: "#e4e4e7",
137 toolbarControl: "#ffffff",
138 toolbarControlForeground: "#27272a",
139 toolbarControlHover: "#f4f4f5",
140 surface: "#ffffff",
141 surfaceRaised: "#fcfcfc",
142 surfaceOverlay: "#ffffff",
143 text: "#27272a",
144 textMuted: "#71717b",
145 border: "#e4e4e7",
146 input: "#d4d4d8",
147 focus: "#1b4ed8",
148 accent: "#1b4ed8",
149 accentForeground: "#ffffff",
150 secondary: "#fafafa",
151 secondaryForeground: "#27272a",
152 muted: "#fafafa",
153 mutedForeground: "#71717b",
154 placeholder: "#71717b",
155 secondaryLabel: "#71717b",
156 iconMuted: "#71717b",
157 error: "#fb2c36",
158 errorForeground: "#c10007",
159 errorSurface: "#fcebec",
160 warning: "#fe9a00",
161 warningForeground: "#bb4d00",
162 warningSurface: "#fcf4e8",
163 update: "#1b4ed8",
164 updateForeground: "#1b4ed8",
165 updateSurface: "#e0e6f7",
166 accentSurface: "#f4f4f5",
167 accentSurfaceForeground: "#18181b",
168 messageSurface: "#f4f4f5",
169 messageForeground: "#27272a",
170 messageAction: "#1b4ed8",
171 messageActionForeground: "#ffffff",
172 messageActionHover: "#3160db",
173 codeBackground: "#ffffff",
174 codeForeground: "#27272a",
175 sidebar: "#fafafa",
176 sidebarForeground: "#27272a",
177 sidebarMutedForeground: "#71717b",
178 sidebarControlSurface: "#f4f4f5",
179 sidebarRowHover: "#fcfcfc",
180 sidebarRowActive: "#ffffff",
181 sidebarRowSelected: "#ffffff",
182 sidebarBorder: "#e4e4e7",
183 terminalBackground: "#fcfcfc",
184 terminalForeground: "#27272a",
185 terminalCursor: "#26384e",
186 terminalSelection: "#d0d6dd",
187 terminalScrollbar: "#d6d6d6",
188 terminalScrollbarHover: "#bdbdbd",
189};
190
191export const T3_CODE_DARK_THEME_COLORS: ThemeColors = {
192 canvas: "#0a0a0a",
193 chrome: "#0a0a0a",
194 toolbar: "#0a0a0a",
195 toolbarForeground: "#f5f5f5",
196 toolbarBorder: "#191919",
197 toolbarControl: "#111111",
198 toolbarControlForeground: "#f5f5f5",
199 toolbarControlHover: "#141414",
200 surface: "#111111",
201 surfaceRaised: "#111111",
202 surfaceOverlay: "#111111",
203 text: "#f5f5f5",
204 textMuted: "#818181",
205 border: "#191919",
206 input: "#1e1e1e",
207 focus: "#346bf1",
208 accent: "#346bf1",
209 accentForeground: "#ffffff",
210 secondary: "#111111",
211 secondaryForeground: "#f5f5f5",
212 muted: "#111111",
213 mutedForeground: "#818181",
214 placeholder: "#818181",
215 secondaryLabel: "#818181",
216 iconMuted: "#818181",
217 error: "#fb414a",
218 errorForeground: "#ff6467",
219 errorSurface: "#301214",
220 warning: "#fe9a00",
221 warningForeground: "#ffb900",
222 warningSurface: "#312108",
223 update: "#346bf1",
224 updateForeground: "#51a2ff",
225 updateSurface: "#121b34",
226 accentSurface: "#141414",
227 accentSurfaceForeground: "#f5f5f5",
228 messageSurface: "#141414",
229 messageForeground: "#f5f5f5",
230 messageAction: "#346bf1",
231 messageActionForeground: "#ffffff",
232 messageActionHover: "#3061d9",
233 codeBackground: "#111111",
234 codeForeground: "#f5f5f5",
235 sidebar: "#000000",
236 sidebarForeground: "#f1f3f7",
237 sidebarMutedForeground: "#a3a3a3",
238 sidebarControlSurface: "#0a0a0a",
239 sidebarRowHover: "#131313",
240 sidebarRowActive: "#1a1b1b",
241 sidebarRowSelected: "#111111",
242 sidebarBorder: "#141414",
243 terminalBackground: "#0a0a0a",
244 terminalForeground: "#f5f5f5",
245 terminalCursor: "#b4cbff",
246 terminalSelection: "#343a47",
247 terminalScrollbar: "#222222",
248 terminalScrollbarHover: "#363636",
249};
250
124251export const T3_CHAT_THEME: ThemeDefinition = {
125252 id: "t3-chat",
126253 label: "T3 Chat",