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 @@...@@ -1,36 +1,36 @@
1{1{
2 "colors": {2 "colors": {
3 "primary": "#262626",3 "primary": "#1b4ed8",
4 "background": "#F2F2F7",4 "background": "#fcfcfc",
5 "input": "#FFFFFF",5 "input": "#ffffff",
6 "danger": "#DC2626",6 "danger": "#c10007",
7 "success": "#059669",7 "success": "#059669",
8 "warning": "#D97706",8 "warning": "#bb4d00",
9 "foreground": "#262626",9 "foreground": "#27272a",
10 "mutedForeground": "#737373",10 "mutedForeground": "#71717b",
11 "primaryForeground": "#FFFFFF",11 "primaryForeground": "#ffffff",
12 "inputForeground": "#262626",12 "inputForeground": "#27272a",
13 "neutral": "#F5F5F5",13 "neutral": "#fafafa",
14 "border": "#E5E5EA",14 "border": "#e4e4e7",
15 "ring": "#A3A3A3",15 "ring": "#1b4ed8",
16 "muted": "#F2F2F7",16 "muted": "#fafafa",
17 "shadow": "#000000"17 "shadow": "#000000"
18 },18 },
19 "darkColors": {19 "darkColors": {
20 "primary": "#F5F5F5",20 "primary": "#346bf1",
21 "background": "#0E0E0E",21 "background": "#0a0a0a",
22 "input": "#171717",22 "input": "#111111",
23 "danger": "#FCA5A5",23 "danger": "#ff6467",
24 "success": "#34D399",24 "success": "#34d399",
25 "warning": "#FBBF24",25 "warning": "#ffb900",
26 "foreground": "#F5F5F5",26 "foreground": "#f5f5f5",
27 "mutedForeground": "#A3A3A3",27 "mutedForeground": "#818181",
28 "primaryForeground": "#0A0A0A",28 "primaryForeground": "#ffffff",
29 "inputForeground": "#F5F5F5",29 "inputForeground": "#f5f5f5",
30 "neutral": "#1C1C1C",30 "neutral": "#111111",
31 "border": "#2A2A2A",31 "border": "#191919",
32 "ring": "#525252",32 "ring": "#346bf1",
33 "muted": "#0E0E0E",33 "muted": "#111111",
34 "shadow": "#000000"34 "shadow": "#000000"
35 },35 },
36 "design": {36 "design": {
apps/mobile/generated-uniwind-default-theme-variables.json+150-126
...@@ -1,156 +1,180 @@...@@ -1,156 +1,180 @@
1{1{
2 "light": {2 "light": {
3 "--color-screen": "#f2f2f7",3 "--color-screen": "#fcfcfc",
4 "--color-sheet": "rgba(242, 242, 247, 0.98)",4 "--color-sheet": "rgba(252, 252, 252, 0.98)",
5 "--color-sheet-solid": "#f2f2f7",5 "--color-sheet-solid": "#fcfcfc",
6 "--color-card": "#ffffff",6 "--color-card": "#ffffff",
7 "--color-card-alt": "#f5f5f5",7 "--color-grouped-card": "#f4f4f5",
8 "--color-card-alt": "#fcfcfc",
8 "--color-card-translucent": "rgba(255, 255, 255, 0.8)",9 "--color-card-translucent": "rgba(255, 255, 255, 0.8)",
9 "--color-thread-canvas": "#f5f5f5",10 "--color-thread-canvas": "#fcfcfc",
10 "--color-thread-selected": "#ffffff",11 "--color-thread-selected": "#ffffff",
11 "--color-thread-selected-foreground": "#262626",12 "--color-thread-selected-foreground": "#27272a",
12 "--color-thread-selected-foreground-muted": "#737373",13 "--color-thread-selected-foreground-muted": "#71717b",
13 "--color-composer-panel": "rgba(245, 245, 245, 0.88)",14 "--color-thread-hover": "#fcfcfc",
14 "--color-composer-surface": "rgba(255, 255, 255, 0.94)",15 "--color-row-hover": "#f4f4f5",
15 "--color-composer-border": "rgba(0, 0, 0, 0.1)",16 "--color-composer-panel": "rgba(252, 252, 252, 0.88)",
16 "--color-foreground": "#262626",17 "--color-composer-surface": "rgba(244, 244, 245, 0.94)",
17 "--color-foreground-secondary": "#525252",18 "--color-composer-border": "rgba(228, 228, 231, 0.8)",
18 "--color-foreground-muted": "#737373",19 "--color-foreground": "#27272a",
19 "--color-foreground-tertiary": "#8e8e93",20 "--color-foreground-secondary": "#6f6f79",
20 "--color-border": "rgba(0, 0, 0, 0.08)",21 "--color-foreground-muted": "#6f6f79",
21 "--color-border-subtle": "rgba(0, 0, 0, 0.06)",22 "--color-foreground-tertiary": "#71717b",
22 "--color-separator": "rgba(0, 0, 0, 0.04)",23 "--color-border": "#e4e4e7",
23 "--color-subtle": "rgba(0, 0, 0, 0.04)",24 "--color-focus": "#1b4ed8",
24 "--color-subtle-strong": "rgba(0, 0, 0, 0.08)",25 "--color-border-subtle": "rgba(228, 228, 231, 0.7)",
25 "--color-inline-skill-background": "rgba(217, 70, 239, 0.12)",26 "--color-separator": "rgba(228, 228, 231, 0.55)",
26 "--color-inline-skill-border": "rgba(217, 70, 239, 0.25)",27 "--color-subtle": "#fafafa",
27 "--color-inline-skill-foreground": "#a21caf",28 "--color-subtle-strong": "#fafafa",
28 "--color-primary": "#262626",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",
29 "--color-primary-foreground": "#ffffff",33 "--color-primary-foreground": "#ffffff",
34 "--color-primary-text": "#1b4ed8",
30 "--color-primary-shadow": "#000000",35 "--color-primary-shadow": "#000000",
31 "--color-secondary": "#ffffff",36 "--color-secondary": "#fafafa",
32 "--color-secondary-foreground": "#262626",37 "--color-secondary-foreground": "#27272a",
33 "--color-secondary-border": "rgba(0, 0, 0, 0.08)",38 "--color-secondary-border": "#e4e4e7",
34 "--color-switch-active-track": "#34c759",39 "--color-switch-active-track": "#1b4ed8",
35 "--color-switch-active-thumb": "#ffffff",40 "--color-switch-active-thumb": "#ffffff",
36 "--color-switch-inactive-track": "rgba(0, 0, 0, 0.08)",41 "--color-switch-inactive-track": "#fafafa",
37 "--color-switch-inactive-thumb": "#8e8e93",42 "--color-switch-inactive-thumb": "#71717b",
38 "--color-warning": "#fffbeb",43 "--color-warning": "#fcf4e8",
39 "--color-warning-border": "#fde68a",44 "--color-warning-border": "rgba(254, 154, 0, 0.32)",
40 "--color-warning-foreground": "#b45309",45 "--color-warning-foreground": "#bb4d00",
41 "--color-danger": "#fef2f2",46 "--color-danger": "#fcebec",
42 "--color-danger-border": "rgba(239, 68, 68, 0.12)",47 "--color-danger-border": "rgba(251, 44, 54, 0.32)",
43 "--color-danger-foreground": "#dc2626",48 "--color-danger-foreground": "#c10007",
49 "--color-update": "#e0e6f7",
50 "--color-update-foreground": "#1b4ed8",
44 "--color-input": "#ffffff",51 "--color-input": "#ffffff",
45 "--color-input-border": "rgba(0, 0, 0, 0.1)",52 "--color-input-border": "#d4d4d8",
46 "--color-sidebar-search": "rgba(118, 118, 128, 0.12)",53 "--color-sidebar-search": "#f4f4f5",
47 "--color-placeholder": "#737373",54 "--color-placeholder": "#6f6f79",
48 "--color-icon": "#262626",55 "--color-icon": "#27272a",
49 "--color-icon-muted": "#525252",56 "--color-icon-muted": "#71717b",
50 "--color-icon-subtle": "#a3a3a3",57 "--color-icon-subtle": "#71717b",
51 "--color-header": "rgba(255, 255, 255, 0.97)",58 "--color-header": "rgba(252, 252, 252, 0.97)",
52 "--color-header-border": "rgba(0, 0, 0, 0.06)",59 "--color-header-foreground": "#27272a",
53 "--color-glass-surface": "rgba(255, 255, 255, 0.72)",60 "--color-header-border": "#e4e4e7",
54 "--color-glass-tint": "rgba(255, 255, 255, 0.18)",61 "--color-glass-surface": "rgba(255, 255, 255, 0.74)",
55 "--color-status-bar": "#f2f2f7",62 "--color-glass-fallback": "rgba(244, 244, 245, 0.94)",
56 "--color-md-body": "#111111",63 "--color-glass-tint": "rgba(255, 255, 255, 0.22)",
57 "--color-md-strong": "#000000",64 "--color-status-bar": "#fcfcfc",
58 "--color-md-link": "#2563eb",65 "--color-md-body": "#27272a",
59 "--color-md-blockquote-border": "rgba(0, 0, 0, 0.08)",66 "--color-md-strong": "#27272a",
60 "--color-md-blockquote-bg": "rgba(0, 0, 0, 0.02)",67 "--color-md-link": "#1b4ed8",
61 "--color-md-code-bg": "rgba(0, 0, 0, 0.04)",68 "--color-md-blockquote-border": "#e4e4e7",
62 "--color-md-code-text": "#262626",69 "--color-md-blockquote-bg": "#fafafa",
63 "--color-md-user-code-bg": "rgba(0, 0, 0, 0.04)",70 "--color-md-code-bg": "#ffffff",
64 "--color-md-user-code-text": "#262626",71 "--color-md-code-text": "#27272a",
65 "--color-md-user-fence-bg": "rgba(0, 0, 0, 0.06)",72 "--color-md-user-code-bg": "rgba(39, 39, 42, 0.18)",
66 "--color-md-user-fence-text": "#262626",73 "--color-md-user-code-text": "#27272a",
67 "--color-md-hr": "rgba(0, 0, 0, 0.08)",74 "--color-md-user-fence-bg": "rgba(0, 0, 0, 0.16)",
68 "--color-user-bubble": "#ffffff",75 "--color-md-user-fence-text": "#27272a",
69 "--color-user-bubble-foreground": "#262626",76 "--color-md-hr": "#e4e4e7",
70 "--color-user-bubble-foreground-muted": "rgba(38, 38, 38, 0.78)",77 "--color-user-bubble": "#efeff1",
71 "--color-user-bubble-skill-foreground": "#2563eb",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",
72 "--color-backdrop": "rgba(0, 0, 0, 0.22)",81 "--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",
74 "--color-drawer-shadow": "rgba(0, 0, 0, 0.12)",86 "--color-drawer-shadow": "rgba(0, 0, 0, 0.12)",
75 "--color-dot-separator": "rgba(0, 0, 0, 0.2)",87 "--color-dot-separator": "rgba(113, 113, 123, 0.35)",
76 "--color-wordmark": "#262626",88 "--color-wordmark": "#27272a",
77 "--color-chevron": "rgba(0, 0, 0, 0.2)"89 "--color-chevron": "rgba(113, 113, 123, 0.42)"
78 },90 },
79 "dark": {91 "dark": {
80 "--color-screen": "#0a0a0a",92 "--color-screen": "#0a0a0a",
81 "--color-sheet": "rgba(14, 14, 14, 0.98)",93 "--color-sheet": "rgba(10, 10, 10, 0.98)",
82 "--color-sheet-solid": "#0e0e0e",94 "--color-sheet-solid": "#0a0a0a",
83 "--color-card": "#171717",95 "--color-card": "#111111",
84 "--color-card-alt": "#1c1c1c",96 "--color-grouped-card": "#1a1b1b",
97 "--color-card-alt": "#111111",
85 "--color-card-translucent": "rgba(17, 17, 17, 0.8)",98 "--color-card-translucent": "rgba(17, 17, 17, 0.8)",
86 "--color-thread-canvas": "#1c1c1c",99 "--color-thread-canvas": "#0a0a0a",
87 "--color-thread-selected": "#171717",100 "--color-thread-selected": "#1a1b1b",
88 "--color-thread-selected-foreground": "#f5f5f5",101 "--color-thread-selected-foreground": "#f1f3f7",
89 "--color-thread-selected-foreground-muted": "#8e8e93",102 "--color-thread-selected-foreground-muted": "#a3a3a3",
90 "--color-composer-panel": "rgba(28, 28, 28, 0.92)",103 "--color-thread-hover": "#131313",
91 "--color-composer-surface": "rgba(23, 23, 23, 0.9)",104 "--color-row-hover": "#141414",
92 "--color-composer-border": "rgba(255, 255, 255, 0.08)",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)",
93 "--color-foreground": "#f5f5f5",108 "--color-foreground": "#f5f5f5",
94 "--color-foreground-secondary": "#a3a3a3",109 "--color-foreground-secondary": "#838383",
95 "--color-foreground-muted": "#8e8e93",110 "--color-foreground-muted": "#838383",
96 "--color-foreground-tertiary": "#636366",111 "--color-foreground-tertiary": "#818181",
97 "--color-border": "rgba(255, 255, 255, 0.06)",112 "--color-border": "#191919",
98 "--color-border-subtle": "rgba(255, 255, 255, 0.04)",113 "--color-focus": "#346bf1",
99 "--color-separator": "rgba(255, 255, 255, 0.03)",114 "--color-border-subtle": "rgba(25, 25, 25, 0.7)",
100 "--color-subtle": "rgba(255, 255, 255, 0.04)",115 "--color-separator": "rgba(25, 25, 25, 0.55)",
101 "--color-subtle-strong": "rgba(255, 255, 255, 0.08)",116 "--color-subtle": "#111111",
102 "--color-inline-skill-background": "rgba(217, 70, 239, 0.12)",117 "--color-subtle-strong": "#111111",
103 "--color-inline-skill-border": "rgba(217, 70, 239, 0.25)",118 "--color-inline-skill-background": "#141414",
104 "--color-inline-skill-foreground": "#f0abfc",119 "--color-inline-skill-border": "rgba(52, 107, 241, 0.42)",
105 "--color-primary": "#f5f5f5",120 "--color-inline-skill-foreground": "#f5f5f5",
106 "--color-primary-foreground": "#0a0a0a",121 "--color-primary": "#346bf1",
122 "--color-primary-foreground": "#ffffff",
123 "--color-primary-text": "#4b7cf3",
107 "--color-primary-shadow": "#000000",124 "--color-primary-shadow": "#000000",
108 "--color-secondary": "rgba(255, 255, 255, 0.04)",125 "--color-secondary": "#111111",
109 "--color-secondary-foreground": "#f5f5f5",126 "--color-secondary-foreground": "#f5f5f5",
110 "--color-secondary-border": "rgba(255, 255, 255, 0.06)",127 "--color-secondary-border": "#191919",
111 "--color-switch-active-track": "#30d158",128 "--color-switch-active-track": "#346bf1",
112 "--color-switch-active-thumb": "#ffffff",129 "--color-switch-active-thumb": "#ffffff",
113 "--color-switch-inactive-track": "rgba(255, 255, 255, 0.06)",130 "--color-switch-inactive-track": "#111111",
114 "--color-switch-inactive-thumb": "#8e8e93",131 "--color-switch-inactive-thumb": "#818181",
115 "--color-warning": "rgba(69, 26, 3, 0.4)",132 "--color-warning": "#312108",
116 "--color-warning-border": "rgba(120, 53, 15, 0.6)",133 "--color-warning-border": "rgba(254, 154, 0, 0.32)",
117 "--color-warning-foreground": "#fcd34d",134 "--color-warning-foreground": "#ffb900",
118 "--color-danger": "rgba(239, 68, 68, 0.14)",135 "--color-danger": "#301214",
119 "--color-danger-border": "rgba(248, 113, 113, 0.18)",136 "--color-danger-border": "rgba(251, 65, 74, 0.32)",
120 "--color-danger-foreground": "#fca5a5",137 "--color-danger-foreground": "#ff6467",
121 "--color-input": "#141414",138 "--color-update": "#121b34",
122 "--color-input-border": "rgba(255, 255, 255, 0.08)",139 "--color-update-foreground": "#51a2ff",
123 "--color-sidebar-search": "rgba(118, 118, 128, 0.24)",140 "--color-input": "#111111",
124 "--color-placeholder": "#8e8e93",141 "--color-input-border": "#1e1e1e",
142 "--color-sidebar-search": "#0a0a0a",
143 "--color-placeholder": "#838383",
125 "--color-icon": "#f5f5f5",144 "--color-icon": "#f5f5f5",
126 "--color-icon-muted": "#a3a3a3",145 "--color-icon-muted": "#818181",
127 "--color-icon-subtle": "#8e8e93",146 "--color-icon-subtle": "#818181",
128 "--color-header": "rgba(10, 10, 10, 0.97)",147 "--color-header": "rgba(10, 10, 10, 0.97)",
129 "--color-header-border": "rgba(255, 255, 255, 0.06)",148 "--color-header-foreground": "#f5f5f5",
130 "--color-glass-surface": "rgba(23, 23, 23, 0.78)",149 "--color-header-border": "#191919",
131 "--color-glass-tint": "rgba(23, 23, 23, 0.24)",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)",
132 "--color-status-bar": "#0a0a0a",153 "--color-status-bar": "#0a0a0a",
133 "--color-md-body": "#e5e5e5",154 "--color-md-body": "#f5f5f5",
134 "--color-md-strong": "#f5f5f5",155 "--color-md-strong": "#f5f5f5",
135 "--color-md-link": "#60a5fa",156 "--color-md-link": "#3b70f1",
136 "--color-md-blockquote-border": "rgba(255, 255, 255, 0.1)",157 "--color-md-blockquote-border": "#191919",
137 "--color-md-blockquote-bg": "rgba(255, 255, 255, 0.03)",158 "--color-md-blockquote-bg": "#111111",
138 "--color-md-code-bg": "rgba(255, 255, 255, 0.06)",159 "--color-md-code-bg": "#111111",
139 "--color-md-code-text": "#e5e5e5",160 "--color-md-code-text": "#f5f5f5",
140 "--color-md-user-code-bg": "rgba(255, 255, 255, 0.06)",161 "--color-md-user-code-bg": "rgba(245, 245, 245, 0.18)",
141 "--color-md-user-code-text": "#e5e5e5",162 "--color-md-user-code-text": "#f5f5f5",
142 "--color-md-user-fence-bg": "rgba(255, 255, 255, 0.09)",163 "--color-md-user-fence-bg": "rgba(0, 0, 0, 0.28)",
143 "--color-md-user-fence-text": "#e5e5e5",164 "--color-md-user-fence-text": "#f5f5f5",
144 "--color-md-hr": "rgba(255, 255, 255, 0.08)",165 "--color-md-hr": "#191919",
145 "--color-user-bubble": "#171717",166 "--color-user-bubble": "#161616",
146 "--color-user-bubble-foreground": "#f5f5f5",167 "--color-user-bubble-foreground": "#f5f5f5",
147 "--color-user-bubble-foreground-muted": "rgba(245, 245, 245, 0.78)",168 "--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",
149 "--color-backdrop": "rgba(0, 0, 0, 0.48)",170 "--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",
151 "--color-drawer-shadow": "rgba(0, 0, 0, 0.32)",175 "--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)",
153 "--color-wordmark": "#f5f5f5",177 "--color-wordmark": "#f5f5f5",
154 "--color-chevron": "rgba(255, 255, 255, 0.2)"178 "--color-chevron": "rgba(129, 129, 129, 0.42)"
155 }179 }
156}180}
apps/mobile/generated-uniwind-themes.css+470-176
...@@ -2,6 +2,93 @@...@@ -2,6 +2,93 @@
2@layer theme {2@layer theme {
3 :root {3 :root {
4 @variant light {4 @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);
5 --color-adaptive-amber-50-950-a40: oklch(98.7% 0.022 95.277);92 --color-adaptive-amber-50-950-a40: oklch(98.7% 0.022 95.277);
6 --color-adaptive-amber-200-900-a60: oklch(92.4% 0.12 95.746);93 --color-adaptive-amber-200-900-a60: oklch(92.4% 0.12 95.746);
7 --color-adaptive-amber-500-a12-a16: oklch(76.9% 0.188 70.08 / 12%);94 --color-adaptive-amber-500-a12-a16: oklch(76.9% 0.188 70.08 / 12%);
...@@ -59,14 +146,101 @@...@@ -59,14 +146,101 @@
59 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);146 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);
60 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);147 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);
61 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);148 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);
62 --color-clerk-page: #f2f2f7;149 --color-clerk-page: #fcfcfc;
63 --color-clerk-foreground: #262626;150 --color-clerk-foreground: #27272a;
64 --color-clerk-foreground-muted: #737373;151 --color-clerk-foreground-muted: #71717b;
65 --color-clerk-border: rgba(229, 229, 234, 0.06);152 --color-clerk-border: #e4e4e7;
66 --color-clerk-danger: #dc2626;153 --color-clerk-danger: #c10007;
67 }154 }
68155
69 @variant dark {156 @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);
70 --color-adaptive-amber-50-950-a40: oklch(27.9% 0.077 45.635 / 40%);244 --color-adaptive-amber-50-950-a40: oklch(27.9% 0.077 45.635 / 40%);
71 --color-adaptive-amber-200-900-a60: oklch(41.4% 0.112 45.904 / 60%);245 --color-adaptive-amber-200-900-a60: oklch(41.4% 0.112 45.904 / 60%);
72 --color-adaptive-amber-500-a12-a16: oklch(76.9% 0.188 70.08 / 16%);246 --color-adaptive-amber-500-a12-a16: oklch(76.9% 0.188 70.08 / 16%);
...@@ -124,32 +298,36 @@...@@ -124,32 +298,36 @@
124 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);298 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);
125 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);299 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);
126 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);300 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);
127 --color-clerk-page: #0e0e0e;301 --color-clerk-page: #0a0a0a;
128 --color-clerk-foreground: #f5f5f5;302 --color-clerk-foreground: #f5f5f5;
129 --color-clerk-foreground-muted: #a3a3a3;303 --color-clerk-foreground-muted: #818181;
130 --color-clerk-border: rgba(42, 42, 42, 0.06);304 --color-clerk-border: #191919;
131 --color-clerk-danger: #fca5a5;305 --color-clerk-danger: #ff6467;
132 }306 }
133307
134 @variant t3-chat-light {308 @variant t3-chat-light {
135 --color-screen: #fdf7fd;309 --color-screen: #fdf7fd;
136 --color-sheet: rgba(253, 247, 253, 0.98);310 --color-sheet: rgba(253, 247, 253, 0.98);
137 --color-sheet-solid: #fdf7fd;311 --color-sheet-solid: #fdf7fd;
138 --color-card: #fdfafd;312 --color-card: #faf3fb;
139 --color-card-alt: #faf3fb;313 --color-grouped-card: #faf3fb;
140 --color-card-translucent: rgba(253, 250, 253, 0.8);314 --color-card-alt: #fdfafd;
141 --color-thread-canvas: #faf3fb;315 --color-card-translucent: rgba(250, 243, 251, 0.8);
142 --color-thread-selected: #fdfafd;316 --color-thread-canvas: #fdf7fd;
143 --color-thread-selected-foreground: #501854;317 --color-thread-selected: #f8f8f7;
318 --color-thread-selected-foreground: #454554;
144 --color-thread-selected-foreground-muted: #ac1668;319 --color-thread-selected-foreground-muted: #ac1668;
145 --color-composer-panel: rgba(250, 243, 251, 0.88);320 --color-thread-hover: #f8f8f7;
146 --color-composer-surface: rgba(253, 250, 253, 0.94);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);
147 --color-composer-border: rgba(238, 225, 237, 0.54);324 --color-composer-border: rgba(238, 225, 237, 0.54);
148 --color-foreground: #501854;325 --color-foreground: #501854;
149 --color-foreground-secondary: #ac1668;326 --color-foreground-secondary: #ac1668;
150 --color-foreground-muted: #8d1255;327 --color-foreground-muted: #8d1255;
151 --color-foreground-tertiary: #ac1668;328 --color-foreground-tertiary: #ac1668;
152 --color-border: #eee1ed;329 --color-border: #eee1ed;
330 --color-focus: #db2777;
153 --color-border-subtle: rgba(238, 225, 237, 0.7);331 --color-border-subtle: rgba(238, 225, 237, 0.7);
154 --color-separator: rgba(238, 225, 237, 0.55);332 --color-separator: rgba(238, 225, 237, 0.55);
155 --color-subtle: #eaa7cb;333 --color-subtle: #eaa7cb;
...@@ -159,6 +337,7 @@...@@ -159,6 +337,7 @@
159 --color-inline-skill-foreground: #454554;337 --color-inline-skill-foreground: #454554;
160 --color-primary: #db2777;338 --color-primary: #db2777;
161 --color-primary-foreground: #ffffff;339 --color-primary-foreground: #ffffff;
340 --color-primary-text: #d32572;
162 --color-primary-shadow: #000000;341 --color-primary-shadow: #000000;
163 --color-secondary: #f1c4e6;342 --color-secondary: #f1c4e6;
164 --color-secondary-foreground: #77347c;343 --color-secondary-foreground: #77347c;
...@@ -173,7 +352,9 @@...@@ -173,7 +352,9 @@
173 --color-danger: #fde4f1;352 --color-danger: #fde4f1;
174 --color-danger-border: rgba(247, 8, 108, 0.32);353 --color-danger-border: rgba(247, 8, 108, 0.32);
175 --color-danger-foreground: #9d174d;354 --color-danger-foreground: #9d174d;
176 --color-input: #fdfafd;355 --color-update: #fadfef;
356 --color-update-foreground: #ac1668;
357 --color-input: #faf3fb;
177 --color-input-border: #e7c1dc;358 --color-input-border: #e7c1dc;
178 --color-sidebar-search: #f8f8f7;359 --color-sidebar-search: #f8f8f7;
179 --color-placeholder: #8b5f90;360 --color-placeholder: #8b5f90;
...@@ -181,13 +362,15 @@...@@ -181,13 +362,15 @@
181 --color-icon-muted: #ac1668;362 --color-icon-muted: #ac1668;
182 --color-icon-subtle: #ac1668;363 --color-icon-subtle: #ac1668;
183 --color-header: rgba(253, 247, 253, 0.97);364 --color-header: rgba(253, 247, 253, 0.97);
365 --color-header-foreground: #501854;
184 --color-header-border: #efbdeb;366 --color-header-border: #efbdeb;
185 --color-glass-surface: rgba(255, 255, 255, 0.74);367 --color-glass-surface: rgba(255, 255, 255, 0.74);
368 --color-glass-fallback: rgba(250, 243, 251, 0.94);
186 --color-glass-tint: rgba(255, 255, 255, 0.22);369 --color-glass-tint: rgba(255, 255, 255, 0.22);
187 --color-status-bar: #fdf7fd;370 --color-status-bar: #fdf7fd;
188 --color-md-body: #501854;371 --color-md-body: #501854;
189 --color-md-strong: #501854;372 --color-md-strong: #501854;
190 --color-md-link: #db2777;373 --color-md-link: #d62675;
191 --color-md-blockquote-border: #eee1ed;374 --color-md-blockquote-border: #eee1ed;
192 --color-md-blockquote-bg: #eaa7cb;375 --color-md-blockquote-bg: #eaa7cb;
193 --color-md-code-bg: #f5ecf9;376 --color-md-code-bg: #f5ecf9;
...@@ -202,7 +385,10 @@...@@ -202,7 +385,10 @@
202 --color-user-bubble-foreground-muted: rgba(73, 44, 97, 0.78);385 --color-user-bubble-foreground-muted: rgba(73, 44, 97, 0.78);
203 --color-user-bubble-skill-foreground: #c12269;386 --color-user-bubble-skill-foreground: #c12269;
204 --color-backdrop: rgba(0, 0, 0, 0.22);387 --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;
206 --color-drawer-shadow: rgba(0, 0, 0, 0.12);392 --color-drawer-shadow: rgba(0, 0, 0, 0.12);
207 --color-dot-separator: rgba(172, 22, 104, 0.35);393 --color-dot-separator: rgba(172, 22, 104, 0.35);
208 --color-wordmark: #501854;394 --color-wordmark: #501854;
...@@ -264,32 +450,36 @@...@@ -264,32 +450,36 @@
264 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);450 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);
265 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);451 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);
266 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);452 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);
267 --color-clerk-page: #f2f2f7;453 --color-clerk-page: #fcfcfc;
268 --color-clerk-foreground: #262626;454 --color-clerk-foreground: #27272a;
269 --color-clerk-foreground-muted: #737373;455 --color-clerk-foreground-muted: #71717b;
270 --color-clerk-border: rgba(229, 229, 234, 0.06);456 --color-clerk-border: #e4e4e7;
271 --color-clerk-danger: #dc2626;457 --color-clerk-danger: #c10007;
272 }458 }
273459
274 @variant t3-chat-dark {460 @variant t3-chat-dark {
275 --color-screen: #1f1a24;461 --color-screen: #1f1a24;
276 --color-sheet: rgba(31, 26, 36, 0.98);462 --color-sheet: rgba(31, 26, 36, 0.98);
277 --color-sheet-solid: #1f1a24;463 --color-sheet-solid: #1f1a24;
278 --color-card: #2c2631;464 --color-card: #29232d;
279 --color-card-alt: #29232d;465 --color-grouped-card: #29232d;
280 --color-card-translucent: rgba(44, 38, 49, 0.8);466 --color-card-alt: #2c2631;
281 --color-thread-canvas: #29232d;467 --color-card-translucent: rgba(41, 35, 45, 0.8);
282 --color-thread-selected: #2c2631;468 --color-thread-canvas: #1f1a24;
283 --color-thread-selected-foreground: #f9f8fb;469 --color-thread-selected: #261922;
470 --color-thread-selected-foreground: #f4f4f5;
284 --color-thread-selected-foreground-muted: #e7d0dd;471 --color-thread-selected-foreground-muted: #e7d0dd;
285 --color-composer-panel: rgba(41, 35, 45, 0.92);472 --color-thread-hover: #261922;
286 --color-composer-surface: rgba(44, 38, 49, 0.9);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);
287 --color-composer-border: rgba(39, 36, 44, 0.46);476 --color-composer-border: rgba(39, 36, 44, 0.46);
288 --color-foreground: #f9f8fb;477 --color-foreground: #f9f8fb;
289 --color-foreground-secondary: #e7d0dd;478 --color-foreground-secondary: #e7d0dd;
290 --color-foreground-muted: #e7d0dd;479 --color-foreground-muted: #e7d0dd;
291 --color-foreground-tertiary: #e7d0dd;480 --color-foreground-tertiary: #e7d0dd;
292 --color-border: #27242c;481 --color-border: #27242c;
482 --color-focus: #db2777;
293 --color-border-subtle: rgba(39, 36, 44, 0.7);483 --color-border-subtle: rgba(39, 36, 44, 0.7);
294 --color-separator: rgba(39, 36, 44, 0.55);484 --color-separator: rgba(39, 36, 44, 0.55);
295 --color-subtle: #423a45;485 --color-subtle: #423a45;
...@@ -299,6 +489,7 @@...@@ -299,6 +489,7 @@
299 --color-inline-skill-foreground: #f8f1f5;489 --color-inline-skill-foreground: #f8f1f5;
300 --color-primary: #a3004c;490 --color-primary: #a3004c;
301 --color-primary-foreground: #fbd0e8;491 --color-primary-foreground: #fbd0e8;
492 --color-primary-text: #cc729c;
302 --color-primary-shadow: #000000;493 --color-primary-shadow: #000000;
303 --color-secondary: #362d3d;494 --color-secondary: #362d3d;
304 --color-secondary-foreground: #d4c7e1;495 --color-secondary-foreground: #d4c7e1;
...@@ -313,7 +504,9 @@...@@ -313,7 +504,9 @@
313 --color-danger: #331a2b;504 --color-danger: #331a2b;
314 --color-danger-border: rgba(157, 23, 77, 0.32);505 --color-danger-border: rgba(157, 23, 77, 0.32);
315 --color-danger-foreground: #fbd0e8;506 --color-danger-foreground: #fbd0e8;
316 --color-input: #2c2631;507 --color-update: #37152b;
508 --color-update-foreground: #fbd0e8;
509 --color-input: #29232d;
317 --color-input-border: #302029;510 --color-input-border: #302029;
318 --color-sidebar-search: #261922;511 --color-sidebar-search: #261922;
319 --color-placeholder: #968d9f;512 --color-placeholder: #968d9f;
...@@ -321,13 +514,15 @@...@@ -321,13 +514,15 @@
321 --color-icon-muted: #d4c7e1;514 --color-icon-muted: #d4c7e1;
322 --color-icon-subtle: #e7d0dd;515 --color-icon-subtle: #e7d0dd;
323 --color-header: rgba(31, 26, 36, 0.97);516 --color-header: rgba(31, 26, 36, 0.97);
517 --color-header-foreground: #f9f8fb;
324 --color-header-border: #27242c;518 --color-header-border: #27242c;
325 --color-glass-surface: rgba(16, 10, 14, 0.74);519 --color-glass-surface: rgba(16, 10, 14, 0.74);
520 --color-glass-fallback: rgba(41, 35, 45, 0.9);
326 --color-glass-tint: rgba(16, 10, 14, 0.22);521 --color-glass-tint: rgba(16, 10, 14, 0.22);
327 --color-status-bar: #1f1a24;522 --color-status-bar: #1f1a24;
328 --color-md-body: #f9f8fb;523 --color-md-body: #f9f8fb;
329 --color-md-strong: #f9f8fb;524 --color-md-strong: #f9f8fb;
330 --color-md-link: #a3004c;525 --color-md-link: #c66290;
331 --color-md-blockquote-border: #27242c;526 --color-md-blockquote-border: #27242c;
332 --color-md-blockquote-bg: #423a45;527 --color-md-blockquote-bg: #423a45;
333 --color-md-code-bg: #1f1a24;528 --color-md-code-bg: #1f1a24;
...@@ -342,7 +537,10 @@...@@ -342,7 +537,10 @@
342 --color-user-bubble-foreground-muted: rgba(242, 235, 250, 0.78);537 --color-user-bubble-foreground-muted: rgba(242, 235, 250, 0.78);
343 --color-user-bubble-skill-foreground: #cb709a;538 --color-user-bubble-skill-foreground: #cb709a;
344 --color-backdrop: rgba(0, 0, 0, 0.48);539 --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;
346 --color-drawer-shadow: rgba(0, 0, 0, 0.32);544 --color-drawer-shadow: rgba(0, 0, 0, 0.32);
347 --color-dot-separator: rgba(231, 208, 221, 0.35);545 --color-dot-separator: rgba(231, 208, 221, 0.35);
348 --color-wordmark: #f9f8fb;546 --color-wordmark: #f9f8fb;
...@@ -404,32 +602,36 @@...@@ -404,32 +602,36 @@
404 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);602 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);
405 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);603 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);
406 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);604 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);
407 --color-clerk-page: #0e0e0e;605 --color-clerk-page: #0a0a0a;
408 --color-clerk-foreground: #f5f5f5;606 --color-clerk-foreground: #f5f5f5;
409 --color-clerk-foreground-muted: #a3a3a3;607 --color-clerk-foreground-muted: #818181;
410 --color-clerk-border: rgba(42, 42, 42, 0.06);608 --color-clerk-border: #191919;
411 --color-clerk-danger: #fca5a5;609 --color-clerk-danger: #ff6467;
412 }610 }
413611
414 @variant grove-light {612 @variant grove-light {
415 --color-screen: #f3f7f4;613 --color-screen: #f3f7f4;
416 --color-sheet: rgba(243, 247, 244, 0.98);614 --color-sheet: rgba(243, 247, 244, 0.98);
417 --color-sheet-solid: #f3f7f4;615 --color-sheet-solid: #f3f7f4;
418 --color-card: #ecefed;616 --color-card: #f3f7f4;
419 --color-card-alt: #f3f7f4;617 --color-grouped-card: #f3f7f4;
420 --color-card-translucent: rgba(236, 239, 237, 0.8);618 --color-card-alt: #ecefed;
619 --color-card-translucent: rgba(243, 247, 244, 0.8);
421 --color-thread-canvas: #f3f7f4;620 --color-thread-canvas: #f3f7f4;
422 --color-thread-selected: #ecefed;621 --color-thread-selected: #bad7c9;
423 --color-thread-selected-foreground: #241523;622 --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;
425 --color-composer-panel: rgba(243, 247, 244, 0.88);626 --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);
427 --color-composer-border: rgba(203, 213, 209, 0.54);628 --color-composer-border: rgba(203, 213, 209, 0.54);
428 --color-foreground: #241523;629 --color-foreground: #241523;
429 --color-foreground-secondary: #746c73;630 --color-foreground-secondary: #726a71;
430 --color-foreground-muted: #6e696f;631 --color-foreground-muted: #6e696f;
431 --color-foreground-tertiary: #746c73;632 --color-foreground-tertiary: #746c73;
432 --color-border: #cbd5d1;633 --color-border: #cbd5d1;
634 --color-focus: #1b7d50;
433 --color-border-subtle: rgba(203, 213, 209, 0.7);635 --color-border-subtle: rgba(203, 213, 209, 0.7);
434 --color-separator: rgba(203, 213, 209, 0.55);636 --color-separator: rgba(203, 213, 209, 0.55);
435 --color-subtle: #e6f0ea;637 --color-subtle: #e6f0ea;
...@@ -437,13 +639,14 @@...@@ -437,13 +639,14 @@
437 --color-inline-skill-background: #d5e6dd;639 --color-inline-skill-background: #d5e6dd;
438 --color-inline-skill-border: rgba(27, 125, 80, 0.42);640 --color-inline-skill-border: rgba(27, 125, 80, 0.42);
439 --color-inline-skill-foreground: #241523;641 --color-inline-skill-foreground: #241523;
440 --color-primary: #1b7d50;642 --color-primary: #8f6410;
441 --color-primary-foreground: #fffaff;643 --color-primary-foreground: #fffaff;
644 --color-primary-text: #8f6410;
442 --color-primary-shadow: #000000;645 --color-primary-shadow: #000000;
443 --color-secondary: #e2ede7;646 --color-secondary: #e2ede7;
444 --color-secondary-foreground: #241523;647 --color-secondary-foreground: #241523;
445 --color-secondary-border: #cbd5d1;648 --color-secondary-border: #cbd5d1;
446 --color-switch-active-track: #1b7d50;649 --color-switch-active-track: #8f6410;
447 --color-switch-active-thumb: #fffaff;650 --color-switch-active-thumb: #fffaff;
448 --color-switch-inactive-track: #e2ede7;651 --color-switch-inactive-track: #e2ede7;
449 --color-switch-inactive-thumb: #6e696f;652 --color-switch-inactive-thumb: #6e696f;
...@@ -453,7 +656,9 @@...@@ -453,7 +656,9 @@
453 --color-danger: #f4e7e5;656 --color-danger: #f4e7e5;
454 --color-danger-border: rgba(251, 44, 54, 0.32);657 --color-danger-border: rgba(251, 44, 54, 0.32);
455 --color-danger-foreground: #c10007;658 --color-danger-foreground: #c10007;
456 --color-input: #ecefed;659 --color-update: #d0e3da;
660 --color-update-foreground: #125134;
661 --color-input: #f3f7f4;
457 --color-input-border: #becbc5;662 --color-input-border: #becbc5;
458 --color-sidebar-search: #d3dcd8;663 --color-sidebar-search: #d3dcd8;
459 --color-placeholder: #716971;664 --color-placeholder: #716971;
...@@ -461,13 +666,15 @@...@@ -461,13 +666,15 @@
461 --color-icon-muted: #746c73;666 --color-icon-muted: #746c73;
462 --color-icon-subtle: #746c73;667 --color-icon-subtle: #746c73;
463 --color-header: rgba(243, 247, 244, 0.97);668 --color-header: rgba(243, 247, 244, 0.97);
669 --color-header-foreground: #241523;
464 --color-header-border: #d5e6dd;670 --color-header-border: #d5e6dd;
465 --color-glass-surface: rgba(231, 233, 232, 0.74);671 --color-glass-surface: rgba(231, 233, 232, 0.74);
672 --color-glass-fallback: rgba(243, 247, 244, 0.94);
466 --color-glass-tint: rgba(231, 233, 232, 0.22);673 --color-glass-tint: rgba(231, 233, 232, 0.22);
467 --color-status-bar: #f3f7f4;674 --color-status-bar: #f3f7f4;
468 --color-md-body: #241523;675 --color-md-body: #241523;
469 --color-md-strong: #241523;676 --color-md-strong: #241523;
470 --color-md-link: #1b7d50;677 --color-md-link: #8f6410;
471 --color-md-blockquote-border: #cbd5d1;678 --color-md-blockquote-border: #cbd5d1;
472 --color-md-blockquote-bg: #e6f0ea;679 --color-md-blockquote-bg: #e6f0ea;
473 --color-md-code-bg: #eef1ef;680 --color-md-code-bg: #eef1ef;
...@@ -482,7 +689,10 @@...@@ -482,7 +689,10 @@
482 --color-user-bubble-foreground-muted: rgba(36, 21, 35, 0.78);689 --color-user-bubble-foreground-muted: rgba(36, 21, 35, 0.78);
483 --color-user-bubble-skill-foreground: #815a0e;690 --color-user-bubble-skill-foreground: #815a0e;
484 --color-backdrop: rgba(0, 0, 0, 0.22);691 --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;
486 --color-drawer-shadow: rgba(0, 0, 0, 0.12);696 --color-drawer-shadow: rgba(0, 0, 0, 0.12);
487 --color-dot-separator: rgba(116, 108, 115, 0.35);697 --color-dot-separator: rgba(116, 108, 115, 0.35);
488 --color-wordmark: #241523;698 --color-wordmark: #241523;
...@@ -544,32 +754,36 @@...@@ -544,32 +754,36 @@
544 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);754 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);
545 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);755 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);
546 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);756 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);
547 --color-clerk-page: #f2f2f7;757 --color-clerk-page: #fcfcfc;
548 --color-clerk-foreground: #262626;758 --color-clerk-foreground: #27272a;
549 --color-clerk-foreground-muted: #737373;759 --color-clerk-foreground-muted: #71717b;
550 --color-clerk-border: rgba(229, 229, 234, 0.06);760 --color-clerk-border: #e4e4e7;
551 --color-clerk-danger: #dc2626;761 --color-clerk-danger: #c10007;
552 }762 }
553763
554 @variant grove-dark {764 @variant grove-dark {
555 --color-screen: #1b2821;765 --color-screen: #1b2821;
556 --color-sheet: rgba(27, 40, 33, 0.98);766 --color-sheet: rgba(27, 40, 33, 0.98);
557 --color-sheet-solid: #1b2821;767 --color-sheet-solid: #1b2821;
558 --color-card: #36413c;768 --color-card: #1b2821;
559 --color-card-alt: #1b2821;769 --color-grouped-card: #1b2821;
560 --color-card-translucent: rgba(54, 65, 60, 0.8);770 --color-card-alt: #36413c;
771 --color-card-translucent: rgba(27, 40, 33, 0.8);
561 --color-thread-canvas: #1b2821;772 --color-thread-canvas: #1b2821;
562 --color-thread-selected: #36413c;773 --color-thread-selected: #2f5641;
563 --color-thread-selected-foreground: #fffaff;774 --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;
565 --color-composer-panel: rgba(27, 40, 33, 0.92);778 --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);
567 --color-composer-border: rgba(65, 95, 79, 0.46);780 --color-composer-border: rgba(65, 95, 79, 0.46);
568 --color-foreground: #fffaff;781 --color-foreground: #fffaff;
569 --color-foreground-secondary: #919595;782 --color-foreground-secondary: #a6aaaa;
570 --color-foreground-muted: #9da5a2;783 --color-foreground-muted: #a3aba8;
571 --color-foreground-tertiary: #919595;784 --color-foreground-tertiary: #919595;
572 --color-border: #415f4f;785 --color-border: #415f4f;
786 --color-focus: #69d69a;
573 --color-border-subtle: rgba(65, 95, 79, 0.7);787 --color-border-subtle: rgba(65, 95, 79, 0.7);
574 --color-separator: rgba(65, 95, 79, 0.55);788 --color-separator: rgba(65, 95, 79, 0.55);
575 --color-subtle: #253e31;789 --color-subtle: #253e31;
...@@ -577,13 +791,14 @@...@@ -577,13 +791,14 @@
577 --color-inline-skill-background: #325c46;791 --color-inline-skill-background: #325c46;
578 --color-inline-skill-border: rgba(105, 214, 154, 0.42);792 --color-inline-skill-border: rgba(105, 214, 154, 0.42);
579 --color-inline-skill-foreground: #fffaff;793 --color-inline-skill-foreground: #fffaff;
580 --color-primary: #69d69a;794 --color-primary: #e3b34e;
581 --color-primary-foreground: #241523;795 --color-primary-foreground: #241523;
796 --color-primary-text: #e3b34e;
582 --color-primary-shadow: #000000;797 --color-primary-shadow: #000000;
583 --color-secondary: #2a4b39;798 --color-secondary: #2a4b39;
584 --color-secondary-foreground: #fffaff;799 --color-secondary-foreground: #fffaff;
585 --color-secondary-border: #415f4f;800 --color-secondary-border: #415f4f;
586 --color-switch-active-track: #69d69a;801 --color-switch-active-track: #e3b34e;
587 --color-switch-active-thumb: #241523;802 --color-switch-active-thumb: #241523;
588 --color-switch-inactive-track: #2a4b39;803 --color-switch-inactive-track: #2a4b39;
589 --color-switch-inactive-thumb: #9da5a2;804 --color-switch-inactive-thumb: #9da5a2;
...@@ -593,7 +808,9 @@...@@ -593,7 +808,9 @@
593 --color-danger: #3f2c28;808 --color-danger: #3f2c28;
594 --color-danger-border: rgba(251, 65, 74, 0.32);809 --color-danger-border: rgba(251, 65, 74, 0.32);
595 --color-danger-foreground: #ff6668;810 --color-danger-foreground: #ff6668;
596 --color-input: #36413c;811 --color-update: #345f48;
812 --color-update-foreground: #9ee4be;
813 --color-input: #1b2821;
597 --color-input-border: #4f725f;814 --color-input-border: #4f725f;
598 --color-sidebar-search: #45554d;815 --color-sidebar-search: #45554d;
599 --color-placeholder: #a9abab;816 --color-placeholder: #a9abab;
...@@ -601,13 +818,15 @@...@@ -601,13 +818,15 @@
601 --color-icon-muted: #919595;818 --color-icon-muted: #919595;
602 --color-icon-subtle: #919595;819 --color-icon-subtle: #919595;
603 --color-header: rgba(27, 40, 33, 0.97);820 --color-header: rgba(27, 40, 33, 0.97);
821 --color-header-foreground: #fffaff;
604 --color-header-border: #36654c;822 --color-header-border: #36654c;
605 --color-glass-surface: rgba(68, 77, 73, 0.74);823 --color-glass-surface: rgba(68, 77, 73, 0.74);
824 --color-glass-fallback: rgba(27, 40, 33, 0.9);
606 --color-glass-tint: rgba(68, 77, 73, 0.22);825 --color-glass-tint: rgba(68, 77, 73, 0.22);
607 --color-status-bar: #1b2821;826 --color-status-bar: #1b2821;
608 --color-md-body: #fffaff;827 --color-md-body: #fffaff;
609 --color-md-strong: #fffaff;828 --color-md-strong: #fffaff;
610 --color-md-link: #69d69a;829 --color-md-link: #e3b34e;
611 --color-md-blockquote-border: #415f4f;830 --color-md-blockquote-border: #415f4f;
612 --color-md-blockquote-bg: #253e31;831 --color-md-blockquote-bg: #253e31;
613 --color-md-code-bg: #28342e;832 --color-md-code-bg: #28342e;
...@@ -622,7 +841,10 @@...@@ -622,7 +841,10 @@
622 --color-user-bubble-foreground-muted: rgba(255, 250, 255, 0.78);841 --color-user-bubble-foreground-muted: rgba(255, 250, 255, 0.78);
623 --color-user-bubble-skill-foreground: #eed295;842 --color-user-bubble-skill-foreground: #eed295;
624 --color-backdrop: rgba(0, 0, 0, 0.48);843 --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;
626 --color-drawer-shadow: rgba(0, 0, 0, 0.32);848 --color-drawer-shadow: rgba(0, 0, 0, 0.32);
627 --color-dot-separator: rgba(145, 149, 149, 0.35);849 --color-dot-separator: rgba(145, 149, 149, 0.35);
628 --color-wordmark: #fffaff;850 --color-wordmark: #fffaff;
...@@ -684,32 +906,36 @@...@@ -684,32 +906,36 @@
684 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);906 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);
685 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);907 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);
686 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);908 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);
687 --color-clerk-page: #0e0e0e;909 --color-clerk-page: #0a0a0a;
688 --color-clerk-foreground: #f5f5f5;910 --color-clerk-foreground: #f5f5f5;
689 --color-clerk-foreground-muted: #a3a3a3;911 --color-clerk-foreground-muted: #818181;
690 --color-clerk-border: rgba(42, 42, 42, 0.06);912 --color-clerk-border: #191919;
691 --color-clerk-danger: #fca5a5;913 --color-clerk-danger: #ff6467;
692 }914 }
693915
694 @variant ocean-light {916 @variant ocean-light {
695 --color-screen: #f5f7f8;917 --color-screen: #f5f7f8;
696 --color-sheet: rgba(245, 247, 248, 0.98);918 --color-sheet: rgba(245, 247, 248, 0.98);
697 --color-sheet-solid: #f5f7f8;919 --color-sheet-solid: #f5f7f8;
698 --color-card: #edeff1;920 --color-card: #f5f7f8;
699 --color-card-alt: #f5f7f8;921 --color-grouped-card: #f5f7f8;
700 --color-card-translucent: rgba(237, 239, 241, 0.8);922 --color-card-alt: #edeff1;
923 --color-card-translucent: rgba(245, 247, 248, 0.8);
701 --color-thread-canvas: #f5f7f8;924 --color-thread-canvas: #f5f7f8;
702 --color-thread-selected: #edeff1;925 --color-thread-selected: #bed4e5;
703 --color-thread-selected-foreground: #241523;926 --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;
705 --color-composer-panel: rgba(245, 247, 248, 0.88);930 --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);
707 --color-composer-border: rgba(205, 212, 220, 0.54);932 --color-composer-border: rgba(205, 212, 220, 0.54);
708 --color-foreground: #241523;933 --color-foreground: #241523;
709 --color-foreground-secondary: #746c75;934 --color-foreground-secondary: #726a73;
710 --color-foreground-muted: #6f6873;935 --color-foreground-muted: #6f6873;
711 --color-foreground-tertiary: #746c75;936 --color-foreground-tertiary: #746c75;
712 --color-border: #cdd4dc;937 --color-border: #cdd4dc;
938 --color-focus: #2672af;
713 --color-border-subtle: rgba(205, 212, 220, 0.7);939 --color-border-subtle: rgba(205, 212, 220, 0.7);
714 --color-separator: rgba(205, 212, 220, 0.55);940 --color-separator: rgba(205, 212, 220, 0.55);
715 --color-subtle: #e8eff4;941 --color-subtle: #e8eff4;
...@@ -717,13 +943,14 @@...@@ -717,13 +943,14 @@
717 --color-inline-skill-background: #d8e4ee;943 --color-inline-skill-background: #d8e4ee;
718 --color-inline-skill-border: rgba(38, 114, 175, 0.42);944 --color-inline-skill-border: rgba(38, 114, 175, 0.42);
719 --color-inline-skill-foreground: #241523;945 --color-inline-skill-foreground: #241523;
720 --color-primary: #2672af;946 --color-primary: #0a6f75;
721 --color-primary-foreground: #fffaff;947 --color-primary-foreground: #fffaff;
948 --color-primary-text: #0a6f75;
722 --color-primary-shadow: #000000;949 --color-primary-shadow: #000000;
723 --color-secondary: #e4ecf2;950 --color-secondary: #e4ecf2;
724 --color-secondary-foreground: #241523;951 --color-secondary-foreground: #241523;
725 --color-secondary-border: #cdd4dc;952 --color-secondary-border: #cdd4dc;
726 --color-switch-active-track: #2672af;953 --color-switch-active-track: #0a6f75;
727 --color-switch-active-thumb: #fffaff;954 --color-switch-active-thumb: #fffaff;
728 --color-switch-inactive-track: #e4ecf2;955 --color-switch-inactive-track: #e4ecf2;
729 --color-switch-inactive-thumb: #6f6873;956 --color-switch-inactive-thumb: #6f6873;
...@@ -733,7 +960,9 @@...@@ -733,7 +960,9 @@
733 --color-danger: #f5e6e9;960 --color-danger: #f5e6e9;
734 --color-danger-border: rgba(251, 44, 54, 0.32);961 --color-danger-border: rgba(251, 44, 54, 0.32);
735 --color-danger-foreground: #c10007;962 --color-danger-foreground: #c10007;
736 --color-input: #edeff1;963 --color-update: #d4e1ed;
964 --color-update-foreground: #194a72;
965 --color-input: #f5f7f8;
737 --color-input-border: #c0c9d4;966 --color-input-border: #c0c9d4;
738 --color-sidebar-search: #d5dbe2;967 --color-sidebar-search: #d5dbe2;
739 --color-placeholder: #716972;968 --color-placeholder: #716972;
...@@ -741,13 +970,15 @@...@@ -741,13 +970,15 @@
741 --color-icon-muted: #746c75;970 --color-icon-muted: #746c75;
742 --color-icon-subtle: #746c75;971 --color-icon-subtle: #746c75;
743 --color-header: rgba(245, 247, 248, 0.97);972 --color-header: rgba(245, 247, 248, 0.97);
973 --color-header-foreground: #241523;
744 --color-header-border: #d8e4ee;974 --color-header-border: #d8e4ee;
745 --color-glass-surface: rgba(232, 233, 235, 0.74);975 --color-glass-surface: rgba(232, 233, 235, 0.74);
976 --color-glass-fallback: rgba(245, 247, 248, 0.94);
746 --color-glass-tint: rgba(232, 233, 235, 0.22);977 --color-glass-tint: rgba(232, 233, 235, 0.22);
747 --color-status-bar: #f5f7f8;978 --color-status-bar: #f5f7f8;
748 --color-md-body: #241523;979 --color-md-body: #241523;
749 --color-md-strong: #241523;980 --color-md-strong: #241523;
750 --color-md-link: #2672af;981 --color-md-link: #0a6f75;
751 --color-md-blockquote-border: #cdd4dc;982 --color-md-blockquote-border: #cdd4dc;
752 --color-md-blockquote-bg: #e8eff4;983 --color-md-blockquote-bg: #e8eff4;
753 --color-md-code-bg: #f0f1f3;984 --color-md-code-bg: #f0f1f3;
...@@ -762,7 +993,10 @@...@@ -762,7 +993,10 @@
762 --color-user-bubble-foreground-muted: rgba(36, 21, 35, 0.78);993 --color-user-bubble-foreground-muted: rgba(36, 21, 35, 0.78);
763 --color-user-bubble-skill-foreground: #0a6c72;994 --color-user-bubble-skill-foreground: #0a6c72;
764 --color-backdrop: rgba(0, 0, 0, 0.22);995 --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;
766 --color-drawer-shadow: rgba(0, 0, 0, 0.12);1000 --color-drawer-shadow: rgba(0, 0, 0, 0.12);
767 --color-dot-separator: rgba(116, 108, 117, 0.35);1001 --color-dot-separator: rgba(116, 108, 117, 0.35);
768 --color-wordmark: #241523;1002 --color-wordmark: #241523;
...@@ -824,32 +1058,36 @@...@@ -824,32 +1058,36 @@
824 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);1058 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);
825 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);1059 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);
826 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);1060 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);
827 --color-clerk-page: #f2f2f7;1061 --color-clerk-page: #fcfcfc;
828 --color-clerk-foreground: #262626;1062 --color-clerk-foreground: #27272a;
829 --color-clerk-foreground-muted: #737373;1063 --color-clerk-foreground-muted: #71717b;
830 --color-clerk-border: rgba(229, 229, 234, 0.06);1064 --color-clerk-border: #e4e4e7;
831 --color-clerk-danger: #dc2626;1065 --color-clerk-danger: #c10007;
832 }1066 }
8331067
834 @variant ocean-dark {1068 @variant ocean-dark {
835 --color-screen: #17212b;1069 --color-screen: #17212b;
836 --color-sheet: rgba(23, 33, 43, 0.98);1070 --color-sheet: rgba(23, 33, 43, 0.98);
837 --color-sheet-solid: #17212b;1071 --color-sheet-solid: #17212b;
838 --color-card: #333b45;1072 --color-card: #17212b;
839 --color-card-alt: #17212b;1073 --color-grouped-card: #17212b;
840 --color-card-translucent: rgba(51, 59, 69, 0.8);1074 --color-card-alt: #333b45;
1075 --color-card-translucent: rgba(23, 33, 43, 0.8);
841 --color-thread-canvas: #17212b;1076 --color-thread-canvas: #17212b;
842 --color-thread-selected: #333b45;1077 --color-thread-selected: #2f495f;
843 --color-thread-selected-foreground: #fffaff;1078 --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;
845 --color-composer-panel: rgba(23, 33, 43, 0.92);1082 --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);
847 --color-composer-border: rgba(64, 85, 103, 0.46);1084 --color-composer-border: rgba(64, 85, 103, 0.46);
848 --color-foreground: #fffaff;1085 --color-foreground: #fffaff;
849 --color-foreground-secondary: #8d8f97;1086 --color-foreground-secondary: #a2a3aa;
850 --color-foreground-muted: #969ca6;1087 --color-foreground-muted: #9ea4ad;
851 --color-foreground-tertiary: #8d8f97;1088 --color-foreground-tertiary: #8d8f97;
852 --color-border: #405567;1089 --color-border: #405567;
1090 --color-focus: #70b9ee;
853 --color-border-subtle: rgba(64, 85, 103, 0.7);1091 --color-border-subtle: rgba(64, 85, 103, 0.7);
854 --color-separator: rgba(64, 85, 103, 0.55);1092 --color-separator: rgba(64, 85, 103, 0.55);
855 --color-subtle: #233544;1093 --color-subtle: #233544;
...@@ -857,13 +1095,14 @@...@@ -857,13 +1095,14 @@
857 --color-inline-skill-background: #324e66;1095 --color-inline-skill-background: #324e66;
858 --color-inline-skill-border: rgba(112, 185, 238, 0.42);1096 --color-inline-skill-border: rgba(112, 185, 238, 0.42);
859 --color-inline-skill-foreground: #fffaff;1097 --color-inline-skill-foreground: #fffaff;
860 --color-primary: #70b9ee;1098 --color-primary: #5bd0d6;
861 --color-primary-foreground: #241523;1099 --color-primary-foreground: #241523;
1100 --color-primary-text: #5bd0d6;
862 --color-primary-shadow: #000000;1101 --color-primary-shadow: #000000;
863 --color-secondary: #293f52;1102 --color-secondary: #293f52;
864 --color-secondary-foreground: #fffaff;1103 --color-secondary-foreground: #fffaff;
865 --color-secondary-border: #405567;1104 --color-secondary-border: #405567;
866 --color-switch-active-track: #70b9ee;1105 --color-switch-active-track: #5bd0d6;
867 --color-switch-active-thumb: #241523;1106 --color-switch-active-thumb: #241523;
868 --color-switch-inactive-track: #293f52;1107 --color-switch-inactive-track: #293f52;
869 --color-switch-inactive-thumb: #969ca6;1108 --color-switch-inactive-thumb: #969ca6;
...@@ -873,7 +1112,9 @@...@@ -873,7 +1112,9 @@
873 --color-danger: #3c2630;1112 --color-danger: #3c2630;
874 --color-danger-border: rgba(251, 65, 74, 0.32);1113 --color-danger-border: rgba(251, 65, 74, 0.32);
875 --color-danger-foreground: #ff6467;1114 --color-danger-foreground: #ff6467;
876 --color-input: #333b45;1115 --color-update: #345269;
1116 --color-update-foreground: #a2d2f4;
1117 --color-input: #17212b;
877 --color-input-border: #4f677b;1118 --color-input-border: #4f677b;
878 --color-sidebar-search: #424e5a;1119 --color-sidebar-search: #424e5a;
879 --color-placeholder: #a4a4ac;1120 --color-placeholder: #a4a4ac;
...@@ -881,13 +1122,15 @@...@@ -881,13 +1122,15 @@
881 --color-icon-muted: #8d8f97;1122 --color-icon-muted: #8d8f97;
882 --color-icon-subtle: #8d8f97;1123 --color-icon-subtle: #8d8f97;
883 --color-header: rgba(23, 33, 43, 0.97);1124 --color-header: rgba(23, 33, 43, 0.97);
1125 --color-header-foreground: #fffaff;
884 --color-header-border: #36566f;1126 --color-header-border: #36566f;
885 --color-glass-surface: rgba(65, 72, 81, 0.74);1127 --color-glass-surface: rgba(65, 72, 81, 0.74);
1128 --color-glass-fallback: rgba(23, 33, 43, 0.9);
886 --color-glass-tint: rgba(65, 72, 81, 0.22);1129 --color-glass-tint: rgba(65, 72, 81, 0.22);
887 --color-status-bar: #17212b;1130 --color-status-bar: #17212b;
888 --color-md-body: #fffaff;1131 --color-md-body: #fffaff;
889 --color-md-strong: #fffaff;1132 --color-md-strong: #fffaff;
890 --color-md-link: #70b9ee;1133 --color-md-link: #5bd0d6;
891 --color-md-blockquote-border: #405567;1134 --color-md-blockquote-border: #405567;
892 --color-md-blockquote-bg: #233544;1135 --color-md-blockquote-bg: #233544;
893 --color-md-code-bg: #252e38;1136 --color-md-code-bg: #252e38;
...@@ -902,7 +1145,10 @@...@@ -902,7 +1145,10 @@
902 --color-user-bubble-foreground-muted: rgba(255, 250, 255, 0.78);1145 --color-user-bubble-foreground-muted: rgba(255, 250, 255, 0.78);
903 --color-user-bubble-skill-foreground: #75d8dd;1146 --color-user-bubble-skill-foreground: #75d8dd;
904 --color-backdrop: rgba(0, 0, 0, 0.48);1147 --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;
906 --color-drawer-shadow: rgba(0, 0, 0, 0.32);1152 --color-drawer-shadow: rgba(0, 0, 0, 0.32);
907 --color-dot-separator: rgba(141, 143, 151, 0.35);1153 --color-dot-separator: rgba(141, 143, 151, 0.35);
908 --color-wordmark: #fffaff;1154 --color-wordmark: #fffaff;
...@@ -964,32 +1210,36 @@...@@ -964,32 +1210,36 @@
964 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);1210 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);
965 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);1211 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);
966 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);1212 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);
967 --color-clerk-page: #0e0e0e;1213 --color-clerk-page: #0a0a0a;
968 --color-clerk-foreground: #f5f5f5;1214 --color-clerk-foreground: #f5f5f5;
969 --color-clerk-foreground-muted: #a3a3a3;1215 --color-clerk-foreground-muted: #818181;
970 --color-clerk-border: rgba(42, 42, 42, 0.06);1216 --color-clerk-border: #191919;
971 --color-clerk-danger: #fca5a5;1217 --color-clerk-danger: #ff6467;
972 }1218 }
9731219
974 @variant ember-light {1220 @variant ember-light {
975 --color-screen: #f9f7f5;1221 --color-screen: #f9f7f5;
976 --color-sheet: rgba(249, 247, 245, 0.98);1222 --color-sheet: rgba(249, 247, 245, 0.98);
977 --color-sheet-solid: #f9f7f5;1223 --color-sheet-solid: #f9f7f5;
978 --color-card: #f1efee;1224 --color-card: #f9f7f5;
979 --color-card-alt: #f9f7f5;1225 --color-grouped-card: #f9f7f5;
980 --color-card-translucent: rgba(241, 239, 238, 0.8);1226 --color-card-alt: #f1efee;
1227 --color-card-translucent: rgba(249, 247, 245, 0.8);
981 --color-thread-canvas: #f9f7f5;1228 --color-thread-canvas: #f9f7f5;
982 --color-thread-selected: #f1efee;1229 --color-thread-selected: #e5ccc0;
983 --color-thread-selected-foreground: #241523;1230 --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;
985 --color-composer-panel: rgba(249, 247, 245, 0.88);1234 --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);
987 --color-composer-border: rgba(221, 210, 206, 0.54);1236 --color-composer-border: rgba(221, 210, 206, 0.54);
988 --color-foreground: #241523;1237 --color-foreground: #241523;
989 --color-foreground-secondary: #766c74;1238 --color-foreground-secondary: #746a72;
990 --color-foreground-muted: #74686f;1239 --color-foreground-muted: #74686f;
991 --color-foreground-tertiary: #766c74;1240 --color-foreground-tertiary: #766c74;
992 --color-border: #ddd2ce;1241 --color-border: #ddd2ce;
1242 --color-focus: #ae552a;
993 --color-border-subtle: rgba(221, 210, 206, 0.7);1243 --color-border-subtle: rgba(221, 210, 206, 0.7);
994 --color-separator: rgba(221, 210, 206, 0.55);1244 --color-separator: rgba(221, 210, 206, 0.55);
995 --color-subtle: #f4ede9;1245 --color-subtle: #f4ede9;
...@@ -997,13 +1247,14 @@...@@ -997,13 +1247,14 @@
997 --color-inline-skill-background: #eee0d9;1247 --color-inline-skill-background: #eee0d9;
998 --color-inline-skill-border: rgba(174, 85, 42, 0.42);1248 --color-inline-skill-border: rgba(174, 85, 42, 0.42);
999 --color-inline-skill-foreground: #241523;1249 --color-inline-skill-foreground: #241523;
1000 --color-primary: #ae552a;1250 --color-primary: #b23535;
1001 --color-primary-foreground: #fffaff;1251 --color-primary-foreground: #fffaff;
1252 --color-primary-text: #b23535;
1002 --color-primary-shadow: #000000;1253 --color-primary-shadow: #000000;
1003 --color-secondary: #f3eae5;1254 --color-secondary: #f3eae5;
1004 --color-secondary-foreground: #241523;1255 --color-secondary-foreground: #241523;
1005 --color-secondary-border: #ddd2ce;1256 --color-secondary-border: #ddd2ce;
1006 --color-switch-active-track: #ae552a;1257 --color-switch-active-track: #b23535;
1007 --color-switch-active-thumb: #fffaff;1258 --color-switch-active-thumb: #fffaff;
1008 --color-switch-inactive-track: #f3eae5;1259 --color-switch-inactive-track: #f3eae5;
1009 --color-switch-inactive-thumb: #74686f;1260 --color-switch-inactive-thumb: #74686f;
...@@ -1013,7 +1264,9 @@...@@ -1013,7 +1264,9 @@
1013 --color-danger: #f9e7e6;1264 --color-danger: #f9e7e6;
1014 --color-danger-border: rgba(251, 44, 54, 0.32);1265 --color-danger-border: rgba(251, 44, 54, 0.32);
1015 --color-danger-foreground: #c10007;1266 --color-danger-foreground: #c10007;
1016 --color-input: #f1efee;1267 --color-update: #edddd5;
1268 --color-update-foreground: #71381b;
1269 --color-input: #f9f7f5;
1017 --color-input-border: #d4c6c1;1270 --color-input-border: #d4c6c1;
1018 --color-sidebar-search: #e2d9d6;1271 --color-sidebar-search: #e2d9d6;
1019 --color-placeholder: #736971;1272 --color-placeholder: #736971;
...@@ -1021,13 +1274,15 @@...@@ -1021,13 +1274,15 @@
1021 --color-icon-muted: #766c74;1274 --color-icon-muted: #766c74;
1022 --color-icon-subtle: #766c74;1275 --color-icon-subtle: #766c74;
1023 --color-header: rgba(249, 247, 245, 0.97);1276 --color-header: rgba(249, 247, 245, 0.97);
1277 --color-header-foreground: #241523;
1024 --color-header-border: #eee0d9;1278 --color-header-border: #eee0d9;
1025 --color-glass-surface: rgba(236, 233, 233, 0.74);1279 --color-glass-surface: rgba(236, 233, 233, 0.74);
1280 --color-glass-fallback: rgba(249, 247, 245, 0.94);
1026 --color-glass-tint: rgba(236, 233, 233, 0.22);1281 --color-glass-tint: rgba(236, 233, 233, 0.22);
1027 --color-status-bar: #f9f7f5;1282 --color-status-bar: #f9f7f5;
1028 --color-md-body: #241523;1283 --color-md-body: #241523;
1029 --color-md-strong: #241523;1284 --color-md-strong: #241523;
1030 --color-md-link: #ae552a;1285 --color-md-link: #b23535;
1031 --color-md-blockquote-border: #ddd2ce;1286 --color-md-blockquote-border: #ddd2ce;
1032 --color-md-blockquote-bg: #f4ede9;1287 --color-md-blockquote-bg: #f4ede9;
1033 --color-md-code-bg: #f3f1f0;1288 --color-md-code-bg: #f3f1f0;
...@@ -1042,7 +1297,10 @@...@@ -1042,7 +1297,10 @@
1042 --color-user-bubble-foreground-muted: rgba(36, 21, 35, 0.78);1297 --color-user-bubble-foreground-muted: rgba(36, 21, 35, 0.78);
1043 --color-user-bubble-skill-foreground: #b13535;1298 --color-user-bubble-skill-foreground: #b13535;
1044 --color-backdrop: rgba(0, 0, 0, 0.22);1299 --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;
1046 --color-drawer-shadow: rgba(0, 0, 0, 0.12);1304 --color-drawer-shadow: rgba(0, 0, 0, 0.12);
1047 --color-dot-separator: rgba(118, 108, 116, 0.35);1305 --color-dot-separator: rgba(118, 108, 116, 0.35);
1048 --color-wordmark: #241523;1306 --color-wordmark: #241523;
...@@ -1104,32 +1362,36 @@...@@ -1104,32 +1362,36 @@
1104 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);1362 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);
1105 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);1363 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);
1106 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);1364 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);
1107 --color-clerk-page: #f2f2f7;1365 --color-clerk-page: #fcfcfc;
1108 --color-clerk-foreground: #262626;1366 --color-clerk-foreground: #27272a;
1109 --color-clerk-foreground-muted: #737373;1367 --color-clerk-foreground-muted: #71717b;
1110 --color-clerk-border: rgba(229, 229, 234, 0.06);1368 --color-clerk-border: #e4e4e7;
1111 --color-clerk-danger: #dc2626;1369 --color-clerk-danger: #c10007;
1112 }1370 }
11131371
1114 @variant ember-dark {1372 @variant ember-dark {
1115 --color-screen: #291e1a;1373 --color-screen: #291e1a;
1116 --color-sheet: rgba(41, 30, 26, 0.98);1374 --color-sheet: rgba(41, 30, 26, 0.98);
1117 --color-sheet-solid: #291e1a;1375 --color-sheet-solid: #291e1a;
1118 --color-card: #433835;1376 --color-card: #291e1a;
1119 --color-card-alt: #291e1a;1377 --color-grouped-card: #291e1a;
1120 --color-card-translucent: rgba(67, 56, 53, 0.8);1378 --color-card-alt: #433835;
1379 --color-card-translucent: rgba(41, 30, 26, 0.8);
1121 --color-thread-canvas: #291e1a;1380 --color-thread-canvas: #291e1a;
1122 --color-thread-selected: #433835;1381 --color-thread-selected: #5d3f2d;
1123 --color-thread-selected-foreground: #fffaff;1382 --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;
1125 --color-composer-panel: rgba(41, 30, 26, 0.92);1386 --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);
1127 --color-composer-border: rgba(102, 76, 63, 0.46);1388 --color-composer-border: rgba(102, 76, 63, 0.46);
1128 --color-foreground: #fffaff;1389 --color-foreground: #fffaff;
1129 --color-foreground-secondary: #968e8f;1390 --color-foreground-secondary: #a8a2a2;
1130 --color-foreground-muted: #a59996;1391 --color-foreground-muted: #aca19f;
1131 --color-foreground-tertiary: #968e8f;1392 --color-foreground-tertiary: #968e8f;
1132 --color-border: #664c3f;1393 --color-border: #664c3f;
1394 --color-focus: #f09a64;
1133 --color-border-subtle: rgba(102, 76, 63, 0.7);1395 --color-border-subtle: rgba(102, 76, 63, 0.7);
1134 --color-separator: rgba(102, 76, 63, 0.55);1396 --color-separator: rgba(102, 76, 63, 0.55);
1135 --color-subtle: #432e23;1397 --color-subtle: #432e23;
...@@ -1137,13 +1399,14 @@...@@ -1137,13 +1399,14 @@
1137 --color-inline-skill-background: #644330;1399 --color-inline-skill-background: #644330;
1138 --color-inline-skill-border: rgba(240, 154, 100, 0.42);1400 --color-inline-skill-border: rgba(240, 154, 100, 0.42);
1139 --color-inline-skill-foreground: #fffaff;1401 --color-inline-skill-foreground: #fffaff;
1140 --color-primary: #f09a64;1402 --color-primary: #f78a7a;
1141 --color-primary-foreground: #241523;1403 --color-primary-foreground: #241523;
1404 --color-primary-text: #f78a7a;
1142 --color-primary-shadow: #000000;1405 --color-primary-shadow: #000000;
1143 --color-secondary: #513728;1406 --color-secondary: #513728;
1144 --color-secondary-foreground: #fffaff;1407 --color-secondary-foreground: #fffaff;
1145 --color-secondary-border: #664c3f;1408 --color-secondary-border: #664c3f;
1146 --color-switch-active-track: #f09a64;1409 --color-switch-active-track: #f78a7a;
1147 --color-switch-active-thumb: #241523;1410 --color-switch-active-thumb: #241523;
1148 --color-switch-inactive-track: #513728;1411 --color-switch-inactive-track: #513728;
1149 --color-switch-inactive-thumb: #a59996;1412 --color-switch-inactive-thumb: #a59996;
...@@ -1153,7 +1416,9 @@...@@ -1153,7 +1416,9 @@
1153 --color-danger: #4a2321;1416 --color-danger: #4a2321;
1154 --color-danger-border: rgba(251, 65, 74, 0.32);1417 --color-danger-border: rgba(251, 65, 74, 0.32);
1155 --color-danger-foreground: #ff6467;1418 --color-danger-foreground: #ff6467;
1156 --color-input: #433835;1419 --color-update: #684631;
1420 --color-update-foreground: #f5bd9a;
1421 --color-input: #291e1a;
1157 --color-input-border: #7a5d4d;1422 --color-input-border: #7a5d4d;
1158 --color-sidebar-search: #584943;1423 --color-sidebar-search: #584943;
1159 --color-placeholder: #aba3a5;1424 --color-placeholder: #aba3a5;
...@@ -1161,13 +1426,15 @@...@@ -1161,13 +1426,15 @@
1161 --color-icon-muted: #968e8f;1426 --color-icon-muted: #968e8f;
1162 --color-icon-subtle: #968e8f;1427 --color-icon-subtle: #968e8f;
1163 --color-header: rgba(41, 30, 26, 0.97);1428 --color-header: rgba(41, 30, 26, 0.97);
1429 --color-header-foreground: #fffaff;
1164 --color-header-border: #6e4934;1430 --color-header-border: #6e4934;
1165 --color-glass-surface: rgba(79, 69, 67, 0.74);1431 --color-glass-surface: rgba(79, 69, 67, 0.74);
1432 --color-glass-fallback: rgba(41, 30, 26, 0.9);
1166 --color-glass-tint: rgba(79, 69, 67, 0.22);1433 --color-glass-tint: rgba(79, 69, 67, 0.22);
1167 --color-status-bar: #291e1a;1434 --color-status-bar: #291e1a;
1168 --color-md-body: #fffaff;1435 --color-md-body: #fffaff;
1169 --color-md-strong: #fffaff;1436 --color-md-strong: #fffaff;
1170 --color-md-link: #f09a64;1437 --color-md-link: #f78a7a;
1171 --color-md-blockquote-border: #664c3f;1438 --color-md-blockquote-border: #664c3f;
1172 --color-md-blockquote-bg: #432e23;1439 --color-md-blockquote-bg: #432e23;
1173 --color-md-code-bg: #362b27;1440 --color-md-code-bg: #362b27;
...@@ -1182,7 +1449,10 @@...@@ -1182,7 +1449,10 @@
1182 --color-user-bubble-foreground-muted: rgba(255, 250, 255, 0.78);1449 --color-user-bubble-foreground-muted: rgba(255, 250, 255, 0.78);
1183 --color-user-bubble-skill-foreground: #fab6ad;1450 --color-user-bubble-skill-foreground: #fab6ad;
1184 --color-backdrop: rgba(0, 0, 0, 0.48);1451 --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;
1186 --color-drawer-shadow: rgba(0, 0, 0, 0.32);1456 --color-drawer-shadow: rgba(0, 0, 0, 0.32);
1187 --color-dot-separator: rgba(150, 142, 143, 0.35);1457 --color-dot-separator: rgba(150, 142, 143, 0.35);
1188 --color-wordmark: #fffaff;1458 --color-wordmark: #fffaff;
...@@ -1244,32 +1514,36 @@...@@ -1244,32 +1514,36 @@
1244 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);1514 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);
1245 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);1515 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);
1246 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);1516 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);
1247 --color-clerk-page: #0e0e0e;1517 --color-clerk-page: #0a0a0a;
1248 --color-clerk-foreground: #f5f5f5;1518 --color-clerk-foreground: #f5f5f5;
1249 --color-clerk-foreground-muted: #a3a3a3;1519 --color-clerk-foreground-muted: #818181;
1250 --color-clerk-border: rgba(42, 42, 42, 0.06);1520 --color-clerk-border: #191919;
1251 --color-clerk-danger: #fca5a5;1521 --color-clerk-danger: #ff6467;
1252 }1522 }
12531523
1254 @variant iris-light {1524 @variant iris-light {
1255 --color-screen: #f8f7f9;1525 --color-screen: #f8f7f9;
1256 --color-sheet: rgba(248, 247, 249, 0.98);1526 --color-sheet: rgba(248, 247, 249, 0.98);
1257 --color-sheet-solid: #f8f7f9;1527 --color-sheet-solid: #f8f7f9;
1258 --color-card: #f0eff2;1528 --color-card: #f8f7f9;
1259 --color-card-alt: #f8f7f9;1529 --color-grouped-card: #f8f7f9;
1260 --color-card-translucent: rgba(240, 239, 242, 0.8);1530 --color-card-alt: #f0eff2;
1531 --color-card-translucent: rgba(248, 247, 249, 0.8);
1261 --color-thread-canvas: #f8f7f9;1532 --color-thread-canvas: #f8f7f9;
1262 --color-thread-selected: #f0eff2;1533 --color-thread-selected: #d4cce8;
1263 --color-thread-selected-foreground: #241523;1534 --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;
1265 --color-composer-panel: rgba(248, 247, 249, 0.88);1538 --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);
1267 --color-composer-border: rgba(214, 209, 222, 0.54);1540 --color-composer-border: rgba(214, 209, 222, 0.54);
1268 --color-foreground: #241523;1541 --color-foreground: #241523;
1269 --color-foreground-secondary: #766c76;1542 --color-foreground-secondary: #746a74;
1270 --color-foreground-muted: #726874;1543 --color-foreground-muted: #726874;
1271 --color-foreground-tertiary: #766c76;1544 --color-foreground-tertiary: #766c76;
1272 --color-border: #d6d1de;1545 --color-border: #d6d1de;
1546 --color-focus: #7253b9;
1273 --color-border-subtle: rgba(214, 209, 222, 0.7);1547 --color-border-subtle: rgba(214, 209, 222, 0.7);
1274 --color-separator: rgba(214, 209, 222, 0.55);1548 --color-separator: rgba(214, 209, 222, 0.55);
1275 --color-subtle: #f0edf6;1549 --color-subtle: #f0edf6;
...@@ -1277,13 +1551,14 @@...@@ -1277,13 +1551,14 @@
1277 --color-inline-skill-background: #e5e0f0;1551 --color-inline-skill-background: #e5e0f0;
1278 --color-inline-skill-border: rgba(114, 83, 185, 0.42);1552 --color-inline-skill-border: rgba(114, 83, 185, 0.42);
1279 --color-inline-skill-foreground: #241523;1553 --color-inline-skill-foreground: #241523;
1280 --color-primary: #7253b9;1554 --color-primary: #a82c87;
1281 --color-primary-foreground: #fffaff;1555 --color-primary-foreground: #fffaff;
1556 --color-primary-text: #a82c87;
1282 --color-primary-shadow: #000000;1557 --color-primary-shadow: #000000;
1283 --color-secondary: #edeaf4;1558 --color-secondary: #edeaf4;
1284 --color-secondary-foreground: #241523;1559 --color-secondary-foreground: #241523;
1285 --color-secondary-border: #d6d1de;1560 --color-secondary-border: #d6d1de;
1286 --color-switch-active-track: #7253b9;1561 --color-switch-active-track: #a82c87;
1287 --color-switch-active-thumb: #fffaff;1562 --color-switch-active-thumb: #fffaff;
1288 --color-switch-inactive-track: #edeaf4;1563 --color-switch-inactive-track: #edeaf4;
1289 --color-switch-inactive-thumb: #726874;1564 --color-switch-inactive-thumb: #726874;
...@@ -1293,7 +1568,9 @@...@@ -1293,7 +1568,9 @@
1293 --color-danger: #f8e6ea;1568 --color-danger: #f8e6ea;
1294 --color-danger-border: rgba(251, 44, 54, 0.32);1569 --color-danger-border: rgba(251, 44, 54, 0.32);
1295 --color-danger-foreground: #c10007;1570 --color-danger-foreground: #c10007;
1296 --color-input: #f0eff2;1571 --color-update: #e2ddef;
1572 --color-update-foreground: #4a3678;
1573 --color-input: #f8f7f9;
1297 --color-input-border: #ccc5d6;1574 --color-input-border: #ccc5d6;
1298 --color-sidebar-search: #ddd9e3;1575 --color-sidebar-search: #ddd9e3;
1299 --color-placeholder: #736973;1576 --color-placeholder: #736973;
...@@ -1301,13 +1578,15 @@...@@ -1301,13 +1578,15 @@
1301 --color-icon-muted: #766c76;1578 --color-icon-muted: #766c76;
1302 --color-icon-subtle: #766c76;1579 --color-icon-subtle: #766c76;
1303 --color-header: rgba(248, 247, 249, 0.97);1580 --color-header: rgba(248, 247, 249, 0.97);
1581 --color-header-foreground: #241523;
1304 --color-header-border: #e5e0f0;1582 --color-header-border: #e5e0f0;
1305 --color-glass-surface: rgba(235, 233, 237, 0.74);1583 --color-glass-surface: rgba(235, 233, 237, 0.74);
1584 --color-glass-fallback: rgba(248, 247, 249, 0.94);
1306 --color-glass-tint: rgba(235, 233, 237, 0.22);1585 --color-glass-tint: rgba(235, 233, 237, 0.22);
1307 --color-status-bar: #f8f7f9;1586 --color-status-bar: #f8f7f9;
1308 --color-md-body: #241523;1587 --color-md-body: #241523;
1309 --color-md-strong: #241523;1588 --color-md-strong: #241523;
1310 --color-md-link: #7253b9;1589 --color-md-link: #a82c87;
1311 --color-md-blockquote-border: #d6d1de;1590 --color-md-blockquote-border: #d6d1de;
1312 --color-md-blockquote-bg: #f0edf6;1591 --color-md-blockquote-bg: #f0edf6;
1313 --color-md-code-bg: #f2f1f4;1592 --color-md-code-bg: #f2f1f4;
...@@ -1322,7 +1601,10 @@...@@ -1322,7 +1601,10 @@
1322 --color-user-bubble-foreground-muted: rgba(36, 21, 35, 0.78);1601 --color-user-bubble-foreground-muted: rgba(36, 21, 35, 0.78);
1323 --color-user-bubble-skill-foreground: #a82c87;1602 --color-user-bubble-skill-foreground: #a82c87;
1324 --color-backdrop: rgba(0, 0, 0, 0.22);1603 --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;
1326 --color-drawer-shadow: rgba(0, 0, 0, 0.12);1608 --color-drawer-shadow: rgba(0, 0, 0, 0.12);
1327 --color-dot-separator: rgba(118, 108, 118, 0.35);1609 --color-dot-separator: rgba(118, 108, 118, 0.35);
1328 --color-wordmark: #241523;1610 --color-wordmark: #241523;
...@@ -1384,32 +1666,36 @@...@@ -1384,32 +1666,36 @@
1384 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);1666 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 12%);
1385 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);1667 --color-adaptive-zinc-500-400: oklch(55.2% 0.016 285.938);
1386 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);1668 --color-adaptive-zinc-600-300: oklch(44.2% 0.017 285.786);
1387 --color-clerk-page: #f2f2f7;1669 --color-clerk-page: #fcfcfc;
1388 --color-clerk-foreground: #262626;1670 --color-clerk-foreground: #27272a;
1389 --color-clerk-foreground-muted: #737373;1671 --color-clerk-foreground-muted: #71717b;
1390 --color-clerk-border: rgba(229, 229, 234, 0.06);1672 --color-clerk-border: #e4e4e7;
1391 --color-clerk-danger: #dc2626;1673 --color-clerk-danger: #c10007;
1392 }1674 }
13931675
1394 @variant iris-dark {1676 @variant iris-dark {
1395 --color-screen: #1d1929;1677 --color-screen: #1d1929;
1396 --color-sheet: rgba(29, 25, 41, 0.98);1678 --color-sheet: rgba(29, 25, 41, 0.98);
1397 --color-sheet-solid: #1d1929;1679 --color-sheet-solid: #1d1929;
1398 --color-card: #383443;1680 --color-card: #1d1929;
1399 --color-card-alt: #1d1929;1681 --color-grouped-card: #1d1929;
1400 --color-card-translucent: rgba(56, 52, 67, 0.8);1682 --color-card-alt: #383443;
1683 --color-card-translucent: rgba(29, 25, 41, 0.8);
1401 --color-thread-canvas: #1d1929;1684 --color-thread-canvas: #1d1929;
1402 --color-thread-selected: #383443;1685 --color-thread-selected: #3f345e;
1403 --color-thread-selected-foreground: #fffaff;1686 --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;
1405 --color-composer-panel: rgba(29, 25, 41, 0.92);1690 --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);
1407 --color-composer-border: rgba(77, 67, 102, 0.46);1692 --color-composer-border: rgba(77, 67, 102, 0.46);
1408 --color-foreground: #fffaff;1693 --color-foreground: #fffaff;
1409 --color-foreground-secondary: #8e8a95;1694 --color-foreground-secondary: #a09da6;
1410 --color-foreground-muted: #9690a1;1695 --color-foreground-muted: #a19cab;
1411 --color-foreground-tertiary: #8e8a95;1696 --color-foreground-tertiary: #8e8a95;
1412 --color-border: #4d4366;1697 --color-border: #4d4366;
1698 --color-focus: #9d7df2;
1413 --color-border-subtle: rgba(77, 67, 102, 0.7);1699 --color-border-subtle: rgba(77, 67, 102, 0.7);
1414 --color-separator: rgba(77, 67, 102, 0.55);1700 --color-separator: rgba(77, 67, 102, 0.55);
1415 --color-subtle: #2d2643;1701 --color-subtle: #2d2643;
...@@ -1417,13 +1703,14 @@...@@ -1417,13 +1703,14 @@
1417 --color-inline-skill-background: #433765;1703 --color-inline-skill-background: #433765;
1418 --color-inline-skill-border: rgba(157, 125, 242, 0.42);1704 --color-inline-skill-border: rgba(157, 125, 242, 0.42);
1419 --color-inline-skill-foreground: #fffaff;1705 --color-inline-skill-foreground: #fffaff;
1420 --color-primary: #9d7df2;1706 --color-primary: #f099d8;
1421 --color-primary-foreground: #241523;1707 --color-primary-foreground: #241523;
1708 --color-primary-text: #f099d8;
1422 --color-primary-shadow: #000000;1709 --color-primary-shadow: #000000;
1423 --color-secondary: #362d51;1710 --color-secondary: #362d51;
1424 --color-secondary-foreground: #fffaff;1711 --color-secondary-foreground: #fffaff;
1425 --color-secondary-border: #4d4366;1712 --color-secondary-border: #4d4366;
1426 --color-switch-active-track: #9d7df2;1713 --color-switch-active-track: #f099d8;
1427 --color-switch-active-thumb: #241523;1714 --color-switch-active-thumb: #241523;
1428 --color-switch-inactive-track: #362d51;1715 --color-switch-inactive-track: #362d51;
1429 --color-switch-inactive-thumb: #9690a1;1716 --color-switch-inactive-thumb: #9690a1;
...@@ -1433,7 +1720,9 @@...@@ -1433,7 +1720,9 @@
1433 --color-danger: #40202e;1720 --color-danger: #40202e;
1434 --color-danger-border: rgba(251, 65, 74, 0.32);1721 --color-danger-border: rgba(251, 65, 74, 0.32);
1435 --color-danger-foreground: #ff6467;1722 --color-danger-foreground: #ff6467;
1436 --color-input: #383443;1723 --color-update: #463969;
1724 --color-update-foreground: #bfabf7;
1725 --color-input: #1d1929;
1437 --color-input-border: #5d527b;1726 --color-input-border: #5d527b;
1438 --color-sidebar-search: #494459;1727 --color-sidebar-search: #494459;
1439 --color-placeholder: #a29ea8;1728 --color-placeholder: #a29ea8;
...@@ -1441,13 +1730,15 @@...@@ -1441,13 +1730,15 @@
1441 --color-icon-muted: #8e8a95;1730 --color-icon-muted: #8e8a95;
1442 --color-icon-subtle: #8e8a95;1731 --color-icon-subtle: #8e8a95;
1443 --color-header: rgba(29, 25, 41, 0.97);1732 --color-header: rgba(29, 25, 41, 0.97);
1733 --color-header-foreground: #fffaff;
1444 --color-header-border: #4a3c70;1734 --color-header-border: #4a3c70;
1445 --color-glass-surface: rgba(69, 66, 80, 0.74);1735 --color-glass-surface: rgba(69, 66, 80, 0.74);
1736 --color-glass-fallback: rgba(29, 25, 41, 0.9);
1446 --color-glass-tint: rgba(69, 66, 80, 0.22);1737 --color-glass-tint: rgba(69, 66, 80, 0.22);
1447 --color-status-bar: #1d1929;1738 --color-status-bar: #1d1929;
1448 --color-md-body: #fffaff;1739 --color-md-body: #fffaff;
1449 --color-md-strong: #fffaff;1740 --color-md-strong: #fffaff;
1450 --color-md-link: #9d7df2;1741 --color-md-link: #f099d8;
1451 --color-md-blockquote-border: #4d4366;1742 --color-md-blockquote-border: #4d4366;
1452 --color-md-blockquote-bg: #2d2643;1743 --color-md-blockquote-bg: #2d2643;
1453 --color-md-code-bg: #2a2736;1744 --color-md-code-bg: #2a2736;
...@@ -1462,7 +1753,10 @@...@@ -1462,7 +1753,10 @@
1462 --color-user-bubble-foreground-muted: rgba(255, 250, 255, 0.78);1753 --color-user-bubble-foreground-muted: rgba(255, 250, 255, 0.78);
1463 --color-user-bubble-skill-foreground: #f099d8;1754 --color-user-bubble-skill-foreground: #f099d8;
1464 --color-backdrop: rgba(0, 0, 0, 0.48);1755 --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;
1466 --color-drawer-shadow: rgba(0, 0, 0, 0.32);1760 --color-drawer-shadow: rgba(0, 0, 0, 0.32);
1467 --color-dot-separator: rgba(142, 138, 149, 0.35);1761 --color-dot-separator: rgba(142, 138, 149, 0.35);
1468 --color-wordmark: #fffaff;1762 --color-wordmark: #fffaff;
...@@ -1524,11 +1818,11 @@...@@ -1524,11 +1818,11 @@
1524 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);1818 --color-adaptive-zinc-500-a12-a16: oklch(55.2% 0.016 285.938 / 16%);
1525 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);1819 --color-adaptive-zinc-500-400: oklch(70.5% 0.015 286.067);
1526 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);1820 --color-adaptive-zinc-600-300: oklch(87.1% 0.006 286.286);
1527 --color-clerk-page: #0e0e0e;1821 --color-clerk-page: #0a0a0a;
1528 --color-clerk-foreground: #f5f5f5;1822 --color-clerk-foreground: #f5f5f5;
1529 --color-clerk-foreground-muted: #a3a3a3;1823 --color-clerk-foreground-muted: #818181;
1530 --color-clerk-border: rgba(42, 42, 42, 0.06);1824 --color-clerk-border: #191919;
1531 --color-clerk-danger: #fca5a5;1825 --color-clerk-danger: #ff6467;
1532 }1826 }
1533 }1827 }
1534}1828}
apps/mobile/global.css-222
...@@ -8,228 +8,6 @@...@@ -8,228 +8,6 @@
8 @variant android {8 @variant android {
9 --font-mono: "monospace";9 --font-mono: "monospace";
10 }10 }
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 }
233 }11 }
234}12}
23513
apps/mobile/modules/t3-markdown-text/android/src/main/java/expo/modules/t3markdowntext/T3ContextChip.kt+9-1
...@@ -114,7 +114,15 @@ class T3ContextChip(...@@ -114,7 +114,15 @@ class T3ContextChip(
114 fun color(114 fun color(
115 value: String,115 value: String,
116 fallback: Int116 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
119 private fun blend(accent: Int, base: Int, weight: Float): Int = Color.rgb(127 private fun blend(accent: Int, base: Int, weight: Float): Int = Color.rgb(
120 (Color.red(accent) * weight + Color.red(base) * (1 - weight)).toInt(),128 (Color.red(accent) * weight + Color.red(base) * (1 - weight)).toInt(),
apps/mobile/plugins/withAndroidModernAlertDialog.cjs+15-16
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1const fs = require("node:fs");1const fs = require("node:fs");
2const path = require("node:path");2const path = require("node:path");
3const themeVariables = require("../generated-uniwind-default-theme-variables.json");
3const {4const {
4 AndroidConfig,5 AndroidConfig,
5 withAndroidColors,6 withAndroidColors,
...@@ -11,9 +12,10 @@ const {...@@ -11,9 +12,10 @@ const {
11// React Native's Alert renders an AppCompat AlertDialog on Android, which12// React Native's Alert renders an AppCompat AlertDialog on Android, which
12// inherits the dated framework dialog chrome (square gray panel, teal13// inherits the dated framework dialog chrome (square gray panel, teal
13// all-caps buttons) from the app theme. These resources restyle it with the14// 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-foreground15// generated default palette: card panel, foreground text, readable primary
15// text, --color-primary buttons, DM Sans type. The @font resources referenced16// buttons, DM Sans type. Alert exposes no runtime custom-palette API, so these
16// here are embedded by the expo-font plugin config in app.config.ts.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
18// AppCompat's default dialog window background is an inset rounded rect, so20// AppCompat's default dialog window background is an inset rounded rect, so
19// the replacement keeps the same 16dp inset to preserve the dialog's margins.21// 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"?>...@@ -30,21 +32,18 @@ const DIALOG_BACKGROUND_DRAWABLE = `<?xml version="1.0" encoding="utf-8"?>
30</inset>32</inset>
31`;33`;
3234
33const COLORS = {35const colorsFor = (appearance) => {
34 light: {36 const variables = themeVariables[appearance];
35 background: "#FFFFFF", // --color-card37 return {
36 text: "#262626", // --color-foreground38 background: variables["--color-card"],
37 secondaryText: "#525252", // --color-foreground-secondary39 text: variables["--color-foreground"],
38 buttonText: "#262626", // --color-primary40 secondaryText: variables["--color-foreground-secondary"],
39 },41 buttonText: variables["--color-primary-text"],
40 night: {42 };
41 background: "#171717",
42 text: "#F5F5F5",
43 secondaryText: "#A3A3A3",
44 buttonText: "#F5F5F5",
45 },
46};43};
4744
45const COLORS = { light: colorsFor("light"), night: colorsFor("dark") };
46
48function assignStyleItem(style, name, value) {47function assignStyleItem(style, name, value) {
49 style.item = style.item ?? [];48 style.item = style.item ?? [];
50 const existing = style.item.find((item) => item.$?.name === name);49 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";...@@ -5,17 +5,15 @@ import * as NodePath from "node:path";
5import tailwindColors from "tailwindcss/colors";5import tailwindColors from "tailwindcss/colors";
6import { BUILT_IN_THEME_IDS, type BuiltInThemeId } from "@t3tools/shared/themePalettes";6import { BUILT_IN_THEME_IDS, type BuiltInThemeId } from "@t3tools/shared/themePalettes";
77
8import clerkTheme from "../clerk-theme.json" with { type: "json" };
9import {8import {
9 createMobileThemeVariables,
10 getMobileThemeColors,
10 getMobileThemeVariables,11 getMobileThemeVariables,
11 MOBILE_THEME_VARIABLE_NAMES,12 DEFAULT_MOBILE_THEME_ID,
12 themeColorWithAlpha,
13 type MobileThemeAppearance,13 type MobileThemeAppearance,
14 type MobileThemeVariables,
15} from "../src/lib/mobileTheme.ts";14} from "../src/lib/mobileTheme.ts";
1615
17const APPEARANCES = ["light", "dark"] as const;16const APPEARANCES = ["light", "dark"] as const;
18const GLOBAL_CSS_PATH = NodePath.resolve(import.meta.dirname, "../global.css");
19const GENERATED_CSS_PATH = NodePath.resolve(import.meta.dirname, "../generated-uniwind-themes.css");17const GENERATED_CSS_PATH = NodePath.resolve(import.meta.dirname, "../generated-uniwind-themes.css");
20const GENERATED_NAMES_PATH = NodePath.resolve(18const GENERATED_NAMES_PATH = NodePath.resolve(
21 import.meta.dirname,19 import.meta.dirname,
...@@ -25,6 +23,7 @@ const GENERATED_DEFAULT_VARIABLES_PATH = NodePath.resolve(...@@ -25,6 +23,7 @@ const GENERATED_DEFAULT_VARIABLES_PATH = NodePath.resolve(
25 import.meta.dirname,23 import.meta.dirname,
26 "../generated-uniwind-default-theme-variables.json",24 "../generated-uniwind-default-theme-variables.json",
27);25);
26const GENERATED_CLERK_THEME_PATH = NodePath.resolve(import.meta.dirname, "../clerk-theme.json");
2827
29type TailwindColorFamily = keyof typeof tailwindColors;28type TailwindColorFamily = keyof typeof tailwindColors;
30type TailwindColorShade = 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950;29type TailwindColorShade = 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950;
...@@ -159,13 +158,38 @@ const variablesFor = (themeId: BuiltInThemeId, appearance: MobileThemeAppearance...@@ -159,13 +158,38 @@ const variablesFor = (themeId: BuiltInThemeId, appearance: MobileThemeAppearance
159158
160// Clerk's native screens use one build-time palette per appearance. Custom159// Clerk's native screens use one build-time palette per appearance. Custom
161// profile pages must match it even when the rest of the app uses a named theme.160// 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
162const clerkVariablesFor = (appearance: MobileThemeAppearance) => {186const clerkVariablesFor = (appearance: MobileThemeAppearance) => {
163 const colors = appearance === "dark" ? clerkTheme.darkColors : clerkTheme.colors;187 const colors = clerkColorsFor(appearance);
164 return {188 return {
165 "--color-clerk-page": colors.background.toLowerCase(),189 "--color-clerk-page": colors.background.toLowerCase(),
166 "--color-clerk-foreground": colors.foreground.toLowerCase(),190 "--color-clerk-foreground": colors.foreground.toLowerCase(),
167 "--color-clerk-foreground-muted": colors.mutedForeground.toLowerCase(),191 "--color-clerk-foreground-muted": colors.mutedForeground.toLowerCase(),
168 "--color-clerk-border": themeColorWithAlpha(colors.border, 0.06),192 "--color-clerk-border": colors.border,
169 "--color-clerk-danger": colors.danger.toLowerCase(),193 "--color-clerk-danger": colors.danger.toLowerCase(),
170 };194 };
171};195};
...@@ -181,6 +205,7 @@ export const renderUniwindThemesCSS = () => {...@@ -181,6 +205,7 @@ export const renderUniwindThemesCSS = () => {
181 const variants = [205 const variants = [
182 ...APPEARANCES.map((appearance) =>206 ...APPEARANCES.map((appearance) =>
183 renderVariant(appearance, {207 renderVariant(appearance, {
208 ...getMobileThemeVariables(DEFAULT_MOBILE_THEME_ID, appearance),
184 ...adaptiveVariablesFor(appearance),209 ...adaptiveVariablesFor(appearance),
185 ...clerkVariablesFor(appearance),210 ...clerkVariablesFor(appearance),
186 }),211 }),
...@@ -202,50 +227,27 @@ export const renderUniwindThemesCSS = () => {...@@ -202,50 +227,27 @@ export const renderUniwindThemesCSS = () => {
202 ].join("\n");227 ].join("\n");
203};228};
204229
205const readVariantBody = (css: string, appearance: MobileThemeAppearance): string => {230export const renderDefaultThemeVariablesJSON = () =>
206 const marker = `@variant ${appearance} {`;231 `${JSON.stringify(
207 const markerIndex = css.indexOf(marker);232 Object.fromEntries(
208 if (markerIndex === -1) throw new Error(`Could not find ${marker} in global.css.`);233 APPEARANCES.map((appearance) => [
209234 appearance,
210 const openingBraceIndex = css.indexOf("{", markerIndex);235 getMobileThemeVariables(DEFAULT_MOBILE_THEME_ID, appearance),
211 let depth = 0;236 ]),
212 for (let index = openingBraceIndex; index < css.length; index += 1) {237 ),
213 if (css[index] === "{") depth += 1;238 null,
214 if (css[index] !== "}") continue;239 2,
215 depth -= 1;240 )}\n`;
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`;
240241
241export const getGeneratedUniwindThemeOutputs = (): ReadonlyArray<242export const getGeneratedUniwindThemeOutputs = (): ReadonlyArray<
242 readonly [filename: string, contents: string]243 readonly [filename: string, contents: string]
243> => [244> => [
244 [GENERATED_CSS_PATH, renderUniwindThemesCSS()],245 [GENERATED_CSS_PATH, renderUniwindThemesCSS()],
245 [GENERATED_NAMES_PATH, `${JSON.stringify(customThemeNames, null, 2)}\n`],246 [GENERATED_NAMES_PATH, `${JSON.stringify(customThemeNames, null, 2)}\n`],
247 [GENERATED_DEFAULT_VARIABLES_PATH, renderDefaultThemeVariablesJSON()],
246 [248 [
247 GENERATED_DEFAULT_VARIABLES_PATH,249 GENERATED_CLERK_THEME_PATH,
248 renderDefaultThemeVariablesJSON(NodeFS.readFileSync(GLOBAL_CSS_PATH, "utf8")),250 `${JSON.stringify({ colors: clerkColorsFor("light"), darkColors: clerkColorsFor("dark"), design: { borderRadius: 18 } }, null, 2)}\n`,
249 ],251 ],
250];252];
251253
apps/mobile/scripts/generate-uniwind-themes.test.ts+14-10
...@@ -5,9 +5,10 @@ import { describe, expect, it } from "vite-plus/test";...@@ -5,9 +5,10 @@ import { describe, expect, it } from "vite-plus/test";
5import {5import {
6 customThemeNames,6 customThemeNames,
7 getGeneratedUniwindThemeOutputs,7 getGeneratedUniwindThemeOutputs,
8 readDefaultThemeVariables,8 renderDefaultThemeVariablesJSON,
9 renderUniwindThemesCSS,9 renderUniwindThemesCSS,
10} from "./generate-uniwind-themes.mts";10} from "./generate-uniwind-themes.mts";
11import { readDefaultMobileThemeVariables } from "../src/lib/mobileTheme.test-support";
1112
12describe("generate mobile Uniwind themes", () => {13describe("generate mobile Uniwind themes", () => {
13 it("keeps the committed outputs current", () => {14 it("keeps the committed outputs current", () => {
...@@ -44,12 +45,15 @@ describe("generate mobile Uniwind themes", () => {...@@ -44,12 +45,15 @@ describe("generate mobile Uniwind themes", () => {
44 }45 }
45 });46 });
4647
47 it("generates the default runtime bridge from the authored CSS", () => {48 it("keeps the default runtime bridge and generated CSS on the same palette", () => {
48 const css = NodeFS.readFileSync(NodePath.resolve(import.meta.dirname, "../global.css"), "utf8");49 const variables = JSON.parse(renderDefaultThemeVariablesJSON());
49 const variables = readDefaultThemeVariables(css);
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");
52 expect(variables.dark["--color-screen"]).toBe("#0a0a0a");55 expect(variables.dark["--color-screen"]).toBe("#0a0a0a");
56 expect(variables.dark["--color-drawer"]).toBe("#000000");
53 expect(Object.keys(variables.light)).toEqual(Object.keys(variables.dark));57 expect(Object.keys(variables.light)).toEqual(Object.keys(variables.dark));
54 });58 });
5559
...@@ -77,11 +81,11 @@ describe("generate mobile Uniwind themes", () => {...@@ -77,11 +81,11 @@ describe("generate mobile Uniwind themes", () => {
77 ),81 ),
78 name,82 name,
79 ).toEqual({83 ).toEqual({
80 "--color-clerk-page": isDark ? "#0e0e0e" : "#f2f2f7",84 "--color-clerk-page": isDark ? "#0a0a0a" : "#fcfcfc",
81 "--color-clerk-foreground": isDark ? "#f5f5f5" : "#262626",85 "--color-clerk-foreground": isDark ? "#f5f5f5" : "#27272a",
82 "--color-clerk-foreground-muted": isDark ? "#a3a3a3" : "#737373",86 "--color-clerk-foreground-muted": isDark ? "#818181" : "#71717b",
83 "--color-clerk-border": isDark ? "rgba(42, 42, 42, 0.06)" : "rgba(229, 229, 234, 0.06)",87 "--color-clerk-border": isDark ? "#191919" : "#e4e4e7",
84 "--color-clerk-danger": isDark ? "#fca5a5" : "#dc2626",88 "--color-clerk-danger": isDark ? "#ff6467" : "#c10007",
85 });89 });
86 }90 }
87 });91 });
apps/mobile/src/components/AndroidScreenHeader.tsx+10-2
...@@ -25,7 +25,13 @@ export function AndroidHeaderIconButton(props: {...@@ -25,7 +25,13 @@ export function AndroidHeaderIconButton(props: {
25 readonly disabled?: boolean;25 readonly disabled?: boolean;
26 readonly selected?: boolean;26 readonly selected?: boolean;
27}) {27}) {
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 );
29}35}
3036
31export function AndroidScreenHeader(props: {37export function AndroidScreenHeader(props: {
...@@ -65,6 +71,7 @@ export function AndroidScreenHeader(props: {...@@ -65,6 +71,7 @@ export function AndroidScreenHeader(props: {
65 <MaterialIconButton71 <MaterialIconButton
66 accessibilityLabel="Navigate up"72 accessibilityLabel="Navigate up"
67 icon="arrow.left"73 icon="arrow.left"
74 tintColorClassName="accent-header-foreground"
68 onPress={props.onBack}75 onPress={props.onBack}
69 />76 />
70 ) : null}77 ) : null}
...@@ -72,7 +79,7 @@ export function AndroidScreenHeader(props: {...@@ -72,7 +79,7 @@ export function AndroidScreenHeader(props: {
72 {props.leading}79 {props.leading}
7380
74 <View className={cn("min-w-0 flex-1", !props.onBack && "pl-1")}>81 <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">
76 {props.title}83 {props.title}
77 </Text>84 </Text>
78 {props.subtitle ? (85 {props.subtitle ? (
...@@ -114,6 +121,7 @@ export function AndroidScreenHeader(props: {...@@ -114,6 +121,7 @@ export function AndroidScreenHeader(props: {
114 <MaterialIconButton121 <MaterialIconButton
115 accessibilityLabel="More actions"122 accessibilityLabel="More actions"
116 icon="ellipsis"123 icon="ellipsis"
124 tintColorClassName="accent-header-foreground"
117 onPress={open}125 onPress={open}
118 />126 />
119 )}127 )}
apps/mobile/src/components/AppText.tsx+4-8
...@@ -18,7 +18,7 @@ export function AppText({ className, ...props }: AppTextProps) {...@@ -18,7 +18,7 @@ export function AppText({ className, ...props }: AppTextProps) {
18 return (18 return (
19 <RNText19 <RNText
20 className={cn("font-sans text-foreground", className)}20 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}
22 {...props}22 {...props}
23 />23 />
24 );24 );
...@@ -42,13 +42,9 @@ export function AppTextInput({ className, ref, ...props }: AppTextInputProps) {...@@ -42,13 +42,9 @@ export function AppTextInput({ className, ref, ...props }: AppTextInputProps) {
42 className,42 className,
43 )}43 )}
44 placeholderTextColorClassName="accent-placeholder"44 placeholderTextColorClassName="accent-placeholder"
45 selectionColorClassName={45 selectionColorClassName={"accent-focus/32"}
46 Platform.OS === "android" ? "accent-primary/32" : "accent-foreground-secondary"46 cursorColorClassName={"accent-focus"}
47 }47 selectionHandleColorClassName={Platform.OS === "android" ? "accent-focus" : undefined}
48 cursorColorClassName={
49 Platform.OS === "android" ? "accent-primary" : "accent-foreground-secondary"
50 }
51 selectionHandleColorClassName={Platform.OS === "android" ? "accent-primary" : undefined}
52 {...props}48 {...props}
53 />49 />
54 );50 );
apps/mobile/src/components/ComposerContextSheet.tsx+3-5
...@@ -187,11 +187,9 @@ export function ComposerContextSheet(props: {...@@ -187,11 +187,9 @@ export function ComposerContextSheet(props: {
187 onRequestClose={props.onClose}187 onRequestClose={props.onClose}
188 >188 >
189 <View189 <View
190 style={{190 className={
191 flex: 1,191 Platform.OS === "android" ? "flex-1 justify-end bg-backdrop" : "flex-1 justify-end"
192 justifyContent: "flex-end",192 }
193 backgroundColor: Platform.OS === "android" ? "#00000066" : undefined,
194 }}
195 >193 >
196 {Platform.OS === "android" ? (194 {Platform.OS === "android" ? (
197 <Pressable195 <Pressable
apps/mobile/src/components/ControlPill.tsx+3-1
...@@ -72,7 +72,9 @@ export function ControlPill(props: {...@@ -72,7 +72,9 @@ export function ControlPill(props: {
72 ? props.disabled72 ? props.disabled
73 ? "text-foreground-muted"73 ? "text-foreground-muted"
74 : "text-primary-foreground"74 : "text-primary-foreground"
75 : "",75 : variant === "danger"
76 ? "text-danger-foreground"
77 : "text-foreground",
76 );78 );
7779
78 if (80 if (
apps/mobile/src/components/FilePreview.tsx+1-1
...@@ -46,7 +46,7 @@ function DocumentPreview(props: {...@@ -46,7 +46,7 @@ function DocumentPreview(props: {
46 }, [uri, name, props.source.mimeType, props.source.kind]);46 }, [uri, name, props.source.mimeType, props.source.kind]);
47 return (47 return (
48 <Modal transparent animationType="fade" onRequestClose={props.onRequestClose}>48 <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">
50 <View className="w-full max-w-sm gap-4 rounded-2xl bg-sheet-solid p-6">50 <View className="w-full max-w-sm gap-4 rounded-2xl bg-sheet-solid p-6">
51 <Text className="font-t3-semibold text-foreground">Opening document…</Text>51 <Text className="font-t3-semibold text-foreground">Opening document…</Text>
52 <Text className="text-foreground-muted" numberOfLines={2}>52 <Text className="text-foreground-muted" numberOfLines={2}>
apps/mobile/src/components/GlassBackdrop.tsx+8-10
...@@ -6,12 +6,13 @@ import { themeColorWithAlpha } from "../lib/mobileTheme";...@@ -6,12 +6,13 @@ import { themeColorWithAlpha } from "../lib/mobileTheme";
66
7/** Frosted backdrop for containers that clip their children to their shape. */7/** Frosted backdrop for containers that clip their children to their shape. */
8export function GlassBackdrop(props: { readonly fallbackColor?: ColorValue }) {8export function GlassBackdrop(props: { readonly fallbackColor?: ColorValue }) {
9 const { themeAppearance } = useAppearancePreferences();9 const { themeAppearance, themeVariables } = useAppearancePreferences();
10 const supportsBlur = Platform.OS === "ios";10 const supportsBlur = Platform.OS === "ios";
11 const colorStyle =11 const color = props.fallbackColor ?? themeVariables["--color-glass-fallback"];
12 props.fallbackColor === undefined12 const colorStyle = {
13 ? undefined13 backgroundColor:
14 : { backgroundColor: themeColorWithAlpha(String(props.fallbackColor), 1) };14 supportsBlur || typeof color !== "string" ? color : themeColorWithAlpha(color, 1),
15 };
1516
16 return (17 return (
17 <>18 <>
...@@ -25,11 +26,8 @@ export function GlassBackdrop(props: { readonly fallbackColor?: ColorValue }) {...@@ -25,11 +26,8 @@ export function GlassBackdrop(props: { readonly fallbackColor?: ColorValue }) {
25 ) : null}26 ) : null}
26 <View27 <View
27 pointerEvents="none"28 pointerEvents="none"
28 className="absolute inset-0 bg-card"29 className="absolute inset-0 bg-glass-fallback"
29 style={[30 style={colorStyle}
30 colorStyle,
31 { opacity: supportsBlur ? (themeAppearance === "dark" ? 0.25 : 0.55) : 1 },
32 ]}
33 />31 />
34 </>32 </>
35 );33 );
apps/mobile/src/components/GlassSurface.tsx+4-9
...@@ -1,16 +1,10 @@...@@ -1,16 +1,10 @@
1import { GlassView, isGlassEffectAPIAvailable } from "expo-glass-effect";1import { GlassView, isGlassEffectAPIAvailable } from "expo-glass-effect";
2import type { ReactNode, Ref } from "react";2import type { ReactNode, Ref } from "react";
3import {3import { Platform, View, type ColorValue, type ViewProps, type ViewStyle } from "react-native";
4 Platform,
5 useColorScheme,
6 View,
7 type ColorValue,
8 type ViewProps,
9 type ViewStyle,
10} from "react-native";
11import { withUniwind } from "uniwind";4import { withUniwind } from "uniwind";
125
13import { cn } from "../lib/cn";6import { cn } from "../lib/cn";
7import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider";
14import { GlassBackdrop } from "./GlassBackdrop";8import { GlassBackdrop } from "./GlassBackdrop";
159
16// Explicit mappings keep the native glassEffectStyle enum out of style-array conversion.10// Explicit mappings keep the native glassEffectStyle enum out of style-array conversion.
...@@ -45,7 +39,8 @@ export function GlassSurface({...@@ -45,7 +39,8 @@ export function GlassSurface({
45 style,39 style,
46 ...props40 ...props
47}: GlassSurfaceProps) {41}: GlassSurfaceProps) {
48 const isDarkMode = useColorScheme() === "dark";42 const { themeAppearance } = useAppearancePreferences();
43 const isDarkMode = themeAppearance === "dark";
49 const supportsGlass = Platform.OS === "ios" && isGlassEffectAPIAvailable();44 const supportsGlass = Platform.OS === "ios" && isGlassEffectAPIAvailable();
50 const hasShadow = chrome !== "none" && Platform.OS !== "android";45 const hasShadow = chrome !== "none" && Platform.OS !== "android";
51 const surfaceStyle: ViewStyle = {46 const surfaceStyle: ViewStyle = {
apps/mobile/src/components/MaterialButton.android.tsx+1-1
...@@ -35,7 +35,7 @@ export function MaterialButton(props: MaterialButtonProps) {...@@ -35,7 +35,7 @@ export function MaterialButton(props: MaterialButtonProps) {
35 : tone === "danger"35 : tone === "danger"
36 ? colors["--color-danger-foreground"]36 ? colors["--color-danger-foreground"]
37 : tone === "text"37 : tone === "text"
38 ? colors["--color-primary"]38 ? colors["--color-primary-text"]
39 : colors["--color-secondary-foreground"];39 : colors["--color-secondary-foreground"];
40 return (40 return (
41 <View41 <View
apps/mobile/src/components/MaterialButton.tsx+25-3
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1import { Pressable } from "react-native";1import { Pressable } from "react-native";
2import { AppText } from "./AppText";2import { AppText } from "./AppText";
3import { cn } from "../lib/cn";
34
4export interface MaterialButtonProps {5export interface MaterialButtonProps {
5 readonly label: string;6 readonly label: string;
...@@ -10,15 +11,36 @@ export interface MaterialButtonProps {...@@ -10,15 +11,36 @@ export interface MaterialButtonProps {
10 readonly fullWidth?: boolean;11 readonly fullWidth?: boolean;
11}12}
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
13export function MaterialButton(props: MaterialButtonProps) {21export function MaterialButton(props: MaterialButtonProps) {
22 const [containerClassName, labelClassName] = TONE_CLASS_NAMES[props.tone ?? "secondary"];
23 const disabled = Boolean(props.disabled || props.loading);
14 return (24 return (
15 <Pressable25 <Pressable
16 accessibilityRole="button"26 accessibilityRole="button"
17 disabled={props.disabled}27 accessibilityState={{ disabled }}
28 disabled={disabled}
18 onPress={props.onPress}29 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 )}
20 >35 >
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>
22 </Pressable>44 </Pressable>
23 );45 );
24}46}
apps/mobile/src/components/MaterialConfirmDialog.android.tsx+9-4
...@@ -58,9 +58,9 @@ export function MaterialConfirmDialog(props: MaterialConfirmDialogProps) {...@@ -58,9 +58,9 @@ export function MaterialConfirmDialog(props: MaterialConfirmDialogProps) {
58 colors={{58 colors={{
59 focusedTextColor: colors["--color-foreground"],59 focusedTextColor: colors["--color-foreground"],
60 unfocusedTextColor: colors["--color-foreground"],60 unfocusedTextColor: colors["--color-foreground"],
61 focusedIndicatorColor: colors["--color-primary"],61 focusedIndicatorColor: colors["--color-focus"],
62 unfocusedIndicatorColor: colors["--color-border"],62 unfocusedIndicatorColor: colors["--color-border"],
63 cursorColor: colors["--color-primary"],63 cursorColor: colors["--color-focus"],
64 }}64 }}
65 >65 >
66 <OutlinedTextField.Label>66 <OutlinedTextField.Label>
...@@ -74,7 +74,10 @@ export function MaterialConfirmDialog(props: MaterialConfirmDialogProps) {...@@ -74,7 +74,10 @@ export function MaterialConfirmDialog(props: MaterialConfirmDialogProps) {
74 </AlertDialog.Text>74 </AlertDialog.Text>
75 ) : null}75 ) : null}
76 <AlertDialog.DismissButton>76 <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 >
78 <Text style={bodyTypography}>{props.request.cancelText ?? "Cancel"}</Text>81 <Text style={bodyTypography}>{props.request.cancelText ?? "Cancel"}</Text>
79 </TextButton>82 </TextButton>
80 </AlertDialog.DismissButton>83 </AlertDialog.DismissButton>
...@@ -84,7 +87,9 @@ export function MaterialConfirmDialog(props: MaterialConfirmDialogProps) {...@@ -84,7 +87,9 @@ export function MaterialConfirmDialog(props: MaterialConfirmDialogProps) {
84 enabled={!props.confirmDisabled}87 enabled={!props.confirmDisabled}
85 colors={{88 colors={{
86 contentColor:89 contentColor:
87 colors[props.request.destructive ? "--color-danger" : "--color-primary"],90 colors[
91 props.request.destructive ? "--color-danger-foreground" : "--color-primary-text"
92 ],
88 }}93 }}
89 >94 >
90 <Text style={bodyTypography}>{props.request.confirmText}</Text>95 <Text style={bodyTypography}>{props.request.confirmText}</Text>
apps/mobile/src/components/MaterialFloatingActionButton.android.tsx+3-5
...@@ -25,9 +25,9 @@ export function MaterialFloatingActionButton(props: {...@@ -25,9 +25,9 @@ export function MaterialFloatingActionButton(props: {
25 const { themeAppearance, themeVariables: colors } = useAppearancePreferences();25 const { themeAppearance, themeVariables: colors } = useAppearancePreferences();
26 const typography = useScaledTextRole("footnote");26 const typography = useScaledTextRole("footnote");
27 const primary = props.tone === "primary";27 const primary = props.tone === "primary";
28 const containerColor = colors[primary ? "--color-primary" : "--color-thread-selected"];28 const containerColor = colors[primary ? "--color-primary" : "--color-secondary"];
29 const contentColor =29 const contentColor =
30 colors[primary ? "--color-primary-foreground" : "--color-thread-selected-foreground"];30 colors[primary ? "--color-primary-foreground" : "--color-secondary-foreground"];
31 const Component =31 const Component =
32 props.variant === "extended"32 props.variant === "extended"
33 ? ExtendedFloatingActionButton33 ? ExtendedFloatingActionButton
...@@ -76,9 +76,7 @@ export function MaterialFloatingActionButton(props: {...@@ -76,9 +76,7 @@ export function MaterialFloatingActionButton(props: {
76 <SymbolView76 <SymbolView
77 name={props.icon}77 name={props.icon}
78 size={iconSize}78 size={iconSize}
79 tintColorClassName={79 tintColorClassName={primary ? "accent-primary-foreground" : "accent-secondary-foreground"}
80 primary ? "accent-primary-foreground" : "accent-thread-selected-foreground"
81 }
82 />80 />
83 </View>81 </View>
84 </View>82 </View>
apps/mobile/src/components/MaterialFloatingActionButton.tsx+3-7
...@@ -15,7 +15,7 @@ export function MaterialFloatingActionButton(...@@ -15,7 +15,7 @@ export function MaterialFloatingActionButton(
15 onPress={props.onPress}15 onPress={props.onPress}
16 className={cn(16 className={cn(
17 "min-h-14 min-w-14 flex-row items-center justify-center gap-2 rounded-2xl px-4",17 "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",
19 props.className,19 props.className,
20 )}20 )}
21 style={props.style}21 style={props.style}
...@@ -24,18 +24,14 @@ export function MaterialFloatingActionButton(...@@ -24,18 +24,14 @@ export function MaterialFloatingActionButton(
24 name={props.icon}24 name={props.icon}
25 size={24}25 size={24}
26 tintColorClassName={26 tintColorClassName={
27 props.tone === "primary"27 props.tone === "primary" ? "accent-primary-foreground" : "accent-secondary-foreground"
28 ? "accent-primary-foreground"
29 : "accent-thread-selected-foreground"
30 }28 }
31 />29 />
32 {props.variant === "extended" && props.expanded !== false ? (30 {props.variant === "extended" && props.expanded !== false ? (
33 <AppText31 <AppText
34 className={cn(32 className={cn(
35 "text-sm",33 "text-sm",
36 props.tone === "primary"34 props.tone === "primary" ? "text-primary-foreground" : "text-secondary-foreground",
37 ? "text-primary-foreground"
38 : "text-thread-selected-foreground",
39 )}35 )}
40 >36 >
41 {props.label}37 {props.label}
apps/mobile/src/components/MaterialIconButton.android.tsx+2-1
...@@ -17,6 +17,7 @@ export function MaterialIconButton(props: {...@@ -17,6 +17,7 @@ export function MaterialIconButton(props: {
17 readonly disabled?: boolean;17 readonly disabled?: boolean;
18 readonly selected?: boolean;18 readonly selected?: boolean;
19 readonly variant?: "standard" | "primary" | "tonal" | "danger";19 readonly variant?: "standard" | "primary" | "tonal" | "danger";
20 readonly tintColorClassName?: string;
20}) {21}) {
21 const { themeAppearance, themeVariables: colors } = useAppearancePreferences();22 const { themeAppearance, themeVariables: colors } = useAppearancePreferences();
22 const variant = props.variant ?? "standard";23 const variant = props.variant ?? "standard";
...@@ -40,7 +41,7 @@ export function MaterialIconButton(props: {...@@ -40,7 +41,7 @@ export function MaterialIconButton(props: {
40 ? "accent-danger-foreground"41 ? "accent-danger-foreground"
41 : variant === "tonal"42 : variant === "tonal"
42 ? "accent-secondary-foreground"43 ? "accent-secondary-foreground"
43 : "accent-foreground";44 : (props.tintColorClassName ?? "accent-foreground");
44 return (45 return (
45 <View46 <View
46 accessible47 accessible
apps/mobile/src/components/MaterialIconButton.tsx+31-3
...@@ -1,7 +1,15 @@...@@ -1,7 +1,15 @@
1import { cn } from "../lib/cn";
1import { Pressable } from "react-native";2import { Pressable } from "react-native";
23
3import { SymbolView, type AppSymbolName } from "./AppSymbol";4import { 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
5export function MaterialIconButton(props: {13export function MaterialIconButton(props: {
6 readonly accessibilityLabel: string;14 readonly accessibilityLabel: string;
7 readonly icon: AppSymbolName;15 readonly icon: AppSymbolName;
...@@ -9,15 +17,35 @@ export function MaterialIconButton(props: {...@@ -9,15 +17,35 @@ export function MaterialIconButton(props: {
9 readonly disabled?: boolean;17 readonly disabled?: boolean;
10 readonly selected?: boolean;18 readonly selected?: boolean;
11 readonly variant?: "standard" | "primary" | "tonal" | "danger";19 readonly variant?: "standard" | "primary" | "tonal" | "danger";
20 readonly tintColorClassName?: string;
12}) {21}) {
22 const variant = props.variant ?? "standard";
23 const [containerClassName, iconTintClassName] = VARIANT_CLASS_NAMES[variant];
13 return (24 return (
14 <Pressable25 <Pressable
15 {...props}26 onPress={props.onPress}
27 disabled={props.disabled}
28 accessibilityLabel={props.accessibilityLabel}
16 accessibilityRole="button"29 accessibilityRole="button"
17 accessibilityState={{ disabled: Boolean(props.disabled), selected: props.selected }}30 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 )}
19 >37 >
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 />
21 </Pressable>49 </Pressable>
22 );50 );
23}51}
apps/mobile/src/components/MaterialSearchField.tsx+3-3
...@@ -30,9 +30,9 @@ export function MaterialSearchField({...@@ -30,9 +30,9 @@ export function MaterialSearchField({
30 returnKeyType="search"30 returnKeyType="search"
31 placeholder={placeholder}31 placeholder={placeholder}
32 placeholderTextColorClassName="accent-placeholder"32 placeholderTextColorClassName="accent-placeholder"
33 selectionColorClassName="accent-primary/32"33 selectionColorClassName="accent-focus/32"
34 cursorColorClassName="accent-primary"34 cursorColorClassName="accent-focus"
35 selectionHandleColorClassName="accent-primary"35 selectionHandleColorClassName="accent-focus"
36 className="min-w-0 flex-1 py-2 font-sans text-base text-foreground"36 className="min-w-0 flex-1 py-2 font-sans text-base text-foreground"
37 value={value}37 value={value}
38 onChangeText={onChangeText}38 onChangeText={onChangeText}
apps/mobile/src/components/RowPressable.tsx+5-3
...@@ -9,22 +9,24 @@ import { useHoverGesture } from "../lib/useHoverGesture";...@@ -9,22 +9,24 @@ import { useHoverGesture } from "../lib/useHoverGesture";
9export function RowPressable({9export function RowPressable({
10 children,10 children,
11 className,11 className,
12 interactionClassName = "bg-primary",12 interactionClassName = "bg-row-hover",
13 interactionOpacity = 1,
13 ...props14 ...props
14}: Omit<ComponentProps<typeof Pressable>, "children"> & {15}: Omit<ComponentProps<typeof Pressable>, "children"> & {
15 readonly children: ReactNode;16 readonly children: ReactNode;
16 readonly interactionClassName?: string;17 readonly interactionClassName?: string;
18 readonly interactionOpacity?: number;
17}) {19}) {
18 const { hovered, hoverGesture } = useHoverGesture(props.disabled ?? false);20 const { hovered, hoverGesture } = useHoverGesture(props.disabled ?? false);
19 return (21 return (
20 <GestureDetector gesture={hoverGesture}>22 <GestureDetector gesture={hoverGesture}>
21 <Pressable {...props} className={cn("relative overflow-hidden", className)}>23 <Pressable {...props} className={cn("relative overflow-hidden", className)}>
22 {({ pressed }) => (24 {() => (
23 <>25 <>
24 <View26 <View
25 pointerEvents="none"27 pointerEvents="none"
26 className={cn("absolute inset-0", interactionClassName)}28 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 }}
28 />30 />
29 {children}31 {children}
30 </>32 </>
apps/mobile/src/components/ScreenHeader.android.tsx+4-4
...@@ -53,7 +53,7 @@ export function ScreenHeader(props: ScreenHeaderProps) {...@@ -53,7 +53,7 @@ export function ScreenHeader(props: ScreenHeaderProps) {
53 <SymbolView53 <SymbolView
54 name={menu.icon}54 name={menu.icon}
55 size={16}55 size={16}
56 tintColorClassName="accent-icon"56 tintColorClassName="accent-header-foreground"
57 type="monochrome"57 type="monochrome"
58 />58 />
59 </Pressable>59 </Pressable>
...@@ -95,7 +95,7 @@ export function ScreenHeader(props: ScreenHeaderProps) {...@@ -95,7 +95,7 @@ export function ScreenHeader(props: ScreenHeaderProps) {
95 <SymbolView95 <SymbolView
96 name="chevron.left"96 name="chevron.left"
97 size={24}97 size={24}
98 tintColorClassName="accent-foreground"98 tintColorClassName="accent-header-foreground"
99 type="monochrome"99 type="monochrome"
100 />100 />
101 </Pressable>101 </Pressable>
...@@ -104,7 +104,7 @@ export function ScreenHeader(props: ScreenHeaderProps) {...@@ -104,7 +104,7 @@ export function ScreenHeader(props: ScreenHeaderProps) {
104 <SymbolView104 <SymbolView
105 name="magnifyingglass"105 name="magnifyingglass"
106 size={17}106 size={17}
107 tintColorClassName="accent-icon"107 tintColorClassName="accent-header-foreground"
108 type="monochrome"108 type="monochrome"
109 />109 />
110 <TextInput110 <TextInput
...@@ -114,7 +114,7 @@ export function ScreenHeader(props: ScreenHeaderProps) {...@@ -114,7 +114,7 @@ export function ScreenHeader(props: ScreenHeaderProps) {
114 value={search.value}114 value={search.value}
115 placeholder={search.placeholder}115 placeholder={search.placeholder}
116 placeholderTextColorClassName="accent-placeholder"116 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"
118 />118 />
119 </View>119 </View>
120 {menuView}120 {menuView}
apps/mobile/src/components/SegmentedControl.tsx+2-2
...@@ -40,7 +40,7 @@ export function SegmentedControl<Value extends number | string>(...@@ -40,7 +40,7 @@ export function SegmentedControl<Value extends number | string>(
40 layout={LinearTransition.duration(200)40 layout={LinearTransition.duration(200)
41 .easing(Easing.out(Easing.cubic))41 .easing(Easing.out(Easing.cubic))
42 .reduceMotion(ReduceMotion.System)}42 .reduceMotion(ReduceMotion.System)}
43 className="absolute inset-y-0 rounded-full bg-subtle-strong"43 className="absolute inset-y-0 rounded-full bg-secondary"
44 style={{44 style={{
45 width: `${100 / props.options.length}%`,45 width: `${100 / props.options.length}%`,
46 start: `${46 start: `${
...@@ -70,7 +70,7 @@ export function SegmentedControl<Value extends number | string>(...@@ -70,7 +70,7 @@ export function SegmentedControl<Value extends number | string>(
70 <Text70 <Text
71 className={cn(71 className={cn(
72 compact ? "text-xs" : "text-sm",72 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",
74 )}74 )}
75 >75 >
76 {option.label}76 {option.label}
apps/mobile/src/features/connection/ConnectionStatusDot.tsx+17-10
...@@ -10,10 +10,16 @@ import Animated, {...@@ -10,10 +10,16 @@ import Animated, {
10} from "react-native-reanimated";10} from "react-native-reanimated";
1111
12import type { RemoteClientConnectionState } from "../../lib/connection";12import type { RemoteClientConnectionState } from "../../lib/connection";
13import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider";
14import { themeColorWithAlpha, type MobileThemeVariables } from "../../lib/mobileTheme";
1315
14export type ConnectionStatusDotState = RemoteClientConnectionState;16export type ConnectionStatusDotState = RemoteClientConnectionState;
1517
16function statusDotTone(state: ConnectionStatusDotState): {18function statusDotTone(
19 state: ConnectionStatusDotState,
20 theme: MobileThemeVariables,
21 dark: boolean,
22): {
17 readonly dotColor: string;23 readonly dotColor: string;
18 readonly haloColor: string;24 readonly haloColor: string;
19} {25} {
...@@ -23,25 +29,25 @@ function statusDotTone(state: ConnectionStatusDotState): {...@@ -23,25 +29,25 @@ function statusDotTone(state: ConnectionStatusDotState): {
23 case "available":29 case "available":
24 case "unsupported":30 case "unsupported":
25 return {31 return {
26 dotColor: "#9ca3af",32 dotColor: theme["--color-icon-muted"],
27 haloColor: "rgba(156,163,175,0.42)",33 haloColor: themeColorWithAlpha(theme["--color-icon-muted"], 0.42),
28 };34 };
29 case "connected":35 case "connected":
30 return {36 return {
31 dotColor: "#34d399",37 dotColor: dark ? "#34d399" : "#059669",
32 haloColor: "rgba(52,211,153,0.48)",38 haloColor: themeColorWithAlpha(dark ? "#34d399" : "#059669", 0.48),
33 };39 };
34 case "connecting":40 case "connecting":
35 case "reconnecting":41 case "reconnecting":
36 return {42 return {
37 dotColor: "#f59e0b",43 dotColor: theme["--color-warning-foreground"],
38 haloColor: "rgba(245,158,11,0.5)",44 haloColor: themeColorWithAlpha(theme["--color-warning-foreground"], 0.5),
39 };45 };
40 case "offline":46 case "offline":
41 case "error":47 case "error":
42 return {48 return {
43 dotColor: "#ef4444",49 dotColor: theme["--color-danger-foreground"],
44 haloColor: "rgba(239,68,68,0.48)",50 haloColor: themeColorWithAlpha(theme["--color-danger-foreground"], 0.48),
45 };51 };
46 }52 }
47}53}
...@@ -78,7 +84,8 @@ export function ConnectionStatusDot(props: {...@@ -78,7 +84,8 @@ export function ConnectionStatusDot(props: {
78 readonly size?: number;84 readonly size?: number;
79}) {85}) {
80 const pulseProgress = usePulseAnimation(props.pulse);86 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");
82 const dotSize = props.size ?? 10;89 const dotSize = props.size ?? 10;
83 const haloSize = dotSize + 4;90 const haloSize = dotSize + 4;
84 const containerSize = haloSize + 4;91 const containerSize = haloSize + 4;
apps/mobile/src/features/connection/connectionTone.ts+2-2
...@@ -18,8 +18,8 @@ export function connectionTone(state: RemoteClientConnectionState): StatusTone {...@@ -18,8 +18,8 @@ export function connectionTone(state: RemoteClientConnectionState): StatusTone {
18 case "connecting":18 case "connecting":
19 return {19 return {
20 label: "Connecting",20 label: "Connecting",
21 pillClassName: "bg-primary/10",21 pillClassName: "bg-update",
22 textClassName: "text-foreground-secondary",22 textClassName: "text-update-foreground",
23 };23 };
24 case "unsupported":24 case "unsupported":
25 return {25 return {
apps/mobile/src/features/devices/DevicePreviewRouteScreen.tsx+7-5
...@@ -231,10 +231,10 @@ function DevicePreviewScreen({...@@ -231,10 +231,10 @@ function DevicePreviewScreen({
231 </AppText>231 </AppText>
232 <Pressable232 <Pressable
233 accessibilityRole="button"233 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"
235 onPress={state.refresh}235 onPress={state.refresh}
236 >236 >
237 <AppText>Retry</AppText>237 <AppText className="text-secondary-foreground">Retry</AppText>
238 </Pressable>238 </Pressable>
239 </>239 </>
240 ) : focused && foreground ? (240 ) : focused && foreground ? (
...@@ -278,7 +278,9 @@ function OpenDevicePreview({...@@ -278,7 +278,9 @@ function OpenDevicePreview({
278 background: themeVariables["--color-sheet-solid"],278 background: themeVariables["--color-sheet-solid"],
279 foreground: themeVariables["--color-foreground"],279 foreground: themeVariables["--color-foreground"],
280 muted: themeVariables["--color-foreground-muted"],280 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"],
282 }}284 }}
283 onUnauthorized={onUnauthorized}285 onUnauthorized={onUnauthorized}
284 onInputConnected={onInputConnected}286 onInputConnected={onInputConnected}
...@@ -292,10 +294,10 @@ function OpenDevicePreview({...@@ -292,10 +294,10 @@ function OpenDevicePreview({
292 </AppText>294 </AppText>
293 <Pressable295 <Pressable
294 accessibilityRole="button"296 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"
296 onPress={refresh}298 onPress={refresh}
297 >299 >
298 <AppText>Retry</AppText>300 <AppText className="text-secondary-foreground">Retry</AppText>
299 </Pressable>301 </Pressable>
300 </>302 </>
301 ) : (303 ) : (
apps/mobile/src/features/devices/device-stream-document.ts+2
...@@ -10,6 +10,8 @@ export interface DeviceStreamConfiguration {...@@ -10,6 +10,8 @@ export interface DeviceStreamConfiguration {
10 readonly foreground: string;10 readonly foreground: string;
11 readonly muted: string;11 readonly muted: string;
12 readonly buttonBackground: string;12 readonly buttonBackground: string;
13 readonly buttonForeground: string;
14 readonly buttonBorder: string;
13 };15 };
14}16}
1517
apps/mobile/src/features/devices/device-stream.browser.ts+2-2
...@@ -90,9 +90,9 @@ export function start(configuration: DeviceStreamConfiguration) {...@@ -90,9 +90,9 @@ export function start(configuration: DeviceStreamConfiguration) {
90 Object.assign(retry.style, {90 Object.assign(retry.style, {
91 padding: "12px 24px",91 padding: "12px 24px",
92 borderRadius: "20px",92 borderRadius: "20px",
93 border: "0",93 border: `1px solid ${colors.buttonBorder}`,
94 background: colors.buttonBackground,94 background: colors.buttonBackground,
95 color: colors.foreground,95 color: colors.buttonForeground,
96 font: "inherit",96 font: "inherit",
97 display: "none",97 display: "none",
98 });98 });
apps/mobile/src/features/files/SourceFileSurface.tsx+3-3
...@@ -74,7 +74,7 @@ const HighlightedSourceLine = memo(function HighlightedSourceLine(props: {...@@ -74,7 +74,7 @@ const HighlightedSourceLine = memo(function HighlightedSourceLine(props: {
74 </NativeText>74 </NativeText>
75 <NativeText75 <NativeText
76 selectable76 selectable
77 selectionColorClassName={Platform.OS === "android" ? "accent-primary/32" : undefined}77 selectionColorClassName={Platform.OS === "android" ? "accent-focus/32" : undefined}
78 numberOfLines={props.wordBreak ? undefined : 1}78 numberOfLines={props.wordBreak ? undefined : 1}
79 className="flex-1 font-normal text-foreground"79 className="flex-1 font-normal text-foreground"
80 style={{80 style={{
...@@ -105,7 +105,7 @@ const HighlightedSourceLine = memo(function HighlightedSourceLine(props: {...@@ -105,7 +105,7 @@ const HighlightedSourceLine = memo(function HighlightedSourceLine(props: {
105 key={`${start}:${token.content.length}:${token.color ?? ""}`}105 key={`${start}:${token.content.length}:${token.color ?? ""}`}
106 selectable106 selectable
107 selectionColorClassName={107 selectionColorClassName={
108 Platform.OS === "android" ? "accent-primary/32" : undefined108 Platform.OS === "android" ? "accent-focus/32" : undefined
109 }109 }
110 style={{110 style={{
111 color: token.color ?? undefined,111 color: token.color ?? undefined,
...@@ -279,7 +279,7 @@ function JavaScriptSourceFileSurface(props: SourceFileSurfaceProps) {...@@ -279,7 +279,7 @@ function JavaScriptSourceFileSurface(props: SourceFileSurfaceProps) {
279 <MarkdownTextPrimitive279 <MarkdownTextPrimitive
280 uiTextView280 uiTextView
281 selectable281 selectable
282 selectionColorClassName={Platform.OS === "android" ? "accent-primary/32" : undefined}282 selectionColorClassName={Platform.OS === "android" ? "accent-focus/32" : undefined}
283 style={{283 style={{
284 color: foreground,284 color: foreground,
285 fontFamily: REVIEW_MONO_FONT_FAMILY,285 fontFamily: REVIEW_MONO_FONT_FAMILY,
apps/mobile/src/features/keyboard/CommandPalette.tsx+30-8
...@@ -20,6 +20,7 @@ import { GlassSurface } from "../../components/GlassSurface";...@@ -20,6 +20,7 @@ import { GlassSurface } from "../../components/GlassSurface";
20import { RowPressable } from "../../components/RowPressable";20import { RowPressable } from "../../components/RowPressable";
21import { AppText as Text } from "../../components/AppText";21import { AppText as Text } from "../../components/AppText";
22import { SymbolView, type AppSymbolName } from "../../components/AppSymbol";22import { SymbolView, type AppSymbolName } from "../../components/AppSymbol";
23import { cn } from "../../lib/cn";
23import { scopedProjectKey, scopedThreadKey } from "../../lib/scopedEntities";24import { scopedProjectKey, scopedThreadKey } from "../../lib/scopedEntities";
24import { T3KeyboardCommands } from "../../native/T3KeyboardCommands";25import { T3KeyboardCommands } from "../../native/T3KeyboardCommands";
25import { useProjects, useThreadShell, useThreadShells } from "../../state/entities";26import { useProjects, useThreadShell, useThreadShells } from "../../state/entities";
...@@ -27,6 +28,7 @@ import { useThreadSearch } from "../../state/queries";...@@ -27,6 +28,7 @@ import { useThreadSearch } from "../../state/queries";
27import { useWorkspaceState } from "../../state/workspace";28import { useWorkspaceState } from "../../state/workspace";
28import { useSavedRemoteConnections } from "../../state/use-remote-environment-registry";29import { useSavedRemoteConnections } from "../../state/use-remote-environment-registry";
29import { useAdaptiveWorkspaceLayout } from "../layout/AdaptiveWorkspaceLayout";30import { useAdaptiveWorkspaceLayout } from "../layout/AdaptiveWorkspaceLayout";
31import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider";
30import { ThreadSearchMatchExcerpt } from "../threads/thread-search-match";32import { ThreadSearchMatchExcerpt } from "../threads/thread-search-match";
31import {33import {
32 filterCommandPaletteItems,34 filterCommandPaletteItems,
...@@ -74,35 +76,53 @@ function PaletteRow(props: {...@@ -74,35 +76,53 @@ function PaletteRow(props: {
74 readonly searchQuery: string;76 readonly searchQuery: string;
75 readonly onSelect: () => void;77 readonly onSelect: () => void;
76}) {78}) {
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";
77 return (85 return (
78 <RowPressable86 <RowPressable
79 accessibilityRole="button"87 accessibilityRole="button"
88 interactionOpacity={props.selected ? 0 : 1}
80 accessibilityState={{ selected: props.selected }}89 accessibilityState={{ selected: props.selected }}
81 onPress={props.onSelect}90 onPress={props.onSelect}
82 className={91 className={
83 props.selected92 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"
85 : "mx-2 flex-row items-center gap-3 rounded-xl px-3"94 : "mx-2 flex-row items-center gap-3 rounded-xl px-3"
86 }95 }
87 style={{ height: ROW_HEIGHT }}96 style={{ height: ROW_HEIGHT }}
88 >97 >
89 <View className="w-7 items-center">98 <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 />
91 </View>104 </View>
92 <View className="flex-1">105 <View className="flex-1">
93 <Text numberOfLines={1} className="text-base">106 <Text numberOfLines={1} className={cn("text-base", foregroundClassName)}>
94 {props.item.title}107 {props.item.title}
95 </Text>108 </Text>
96 {props.searchMatch ? (109 {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 />
98 ) : props.item.detail ? (116 ) : props.item.detail ? (
99 <Text numberOfLines={1} className="text-sm text-foreground-muted">117 <Text numberOfLines={1} className={cn("text-sm", mutedForegroundClassName)}>
100 {props.item.detail}118 {props.item.detail}
101 </Text>119 </Text>
102 ) : null}120 ) : null}
103 </View>121 </View>
104 {props.index < 9 ? (122 {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 >
106 ⌘{props.index + 1}126 ⌘{props.index + 1}
107 </NativeText>127 </NativeText>
108 ) : null}128 ) : null}
...@@ -117,6 +137,7 @@ export function CommandPalette(props: {...@@ -117,6 +137,7 @@ export function CommandPalette(props: {
117 readonly onCommand: (command: HardwareKeyboardCommand) => void;137 readonly onCommand: (command: HardwareKeyboardCommand) => void;
118}) {138}) {
119 const navigation = useNavigation();139 const navigation = useNavigation();
140 const { themeVariables } = useAppearancePreferences();
120 const { selectThread } = useAdaptiveWorkspaceLayout();141 const { selectThread } = useAdaptiveWorkspaceLayout();
121 const runCommand = props.onCommand;142 const runCommand = props.onCommand;
122 const projects = useProjects();143 const projects = useProjects();
...@@ -392,14 +413,15 @@ export function CommandPalette(props: {...@@ -392,14 +413,15 @@ export function CommandPalette(props: {
392 className="flex-1 items-center justify-center p-4"413 className="flex-1 items-center justify-center p-4"
393 >414 >
394 <Pressable415 <Pressable
395 className="absolute inset-0 bg-black/15"416 className="absolute inset-0 bg-backdrop"
396 accessibilityLabel="Close command palette"417 accessibilityLabel="Close command palette"
397 onPress={() => close()}418 onPress={() => close()}
398 />419 />
399 <GlassSurface420 <GlassSurface
400 accessibilityViewIsModal421 accessibilityViewIsModal
401 className="bg-sheet/70"422 className="bg-sheet"
402 tintColorClassName="accent-sheet/20"423 tintColorClassName="accent-sheet/20"
424 fallbackColor={themeVariables["--color-sheet"]}
403 style={{425 style={{
404 width: Math.min(600, width - 32),426 width: Math.min(600, width - 32),
405 height: Math.min(520, height - 80),427 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 }) {...@@ -129,7 +129,7 @@ function SectionTitle(props: { readonly children: string }) {
129 <Text129 <Text
130 className={130 className={
131 Platform.OS === "android"131 Platform.OS === "android"
132 ? "px-4 text-sm font-t3-medium text-primary"132 ? "px-4 text-sm font-t3-medium text-primary-text"
133 : "px-1 text-2xs font-t3-bold tracking-[0.7px] uppercase text-foreground-muted"133 : "px-1 text-2xs font-t3-bold tracking-[0.7px] uppercase text-foreground-muted"
134 }134 }
135 >135 >
apps/mobile/src/features/review/ReviewSheet.tsx+5-3
...@@ -262,7 +262,7 @@ const ReviewFileNavigatorRow = memo(function ReviewFileNavigatorRow(props: {...@@ -262,7 +262,7 @@ const ReviewFileNavigatorRow = memo(function ReviewFileNavigatorRow(props: {
262 Platform.OS === "android"262 Platform.OS === "android"
263 ? cn(263 ? cn(
264 "mt-1 min-h-12 justify-center rounded-[20px] px-3 py-2 active:bg-subtle",264 "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",
266 )266 )
267 : selected267 : selected
268 ? "mt-1 min-h-12 justify-center rounded-xl bg-subtle-strong px-3 py-2"268 ? "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: {...@@ -281,8 +281,10 @@ const ReviewFileNavigatorRow = memo(function ReviewFileNavigatorRow(props: {
281 {file.path}281 {file.path}
282 </Text>282 </Text>
283 <View className="mt-1 flex-row gap-2">283 <View className="mt-1 flex-row gap-2">
284 <Text className="text-2xs font-t3-bold text-emerald-600">+{file.additions}</Text>284 <Text className="text-2xs font-t3-bold text-adaptive-emerald-700-300">
285 <Text className="text-2xs font-t3-bold text-rose-600">-{file.deletions}</Text>285 +{file.additions}
286 </Text>
287 <Text className="text-2xs font-t3-bold text-adaptive-rose-700-300">-{file.deletions}</Text>
286 </View>288 </View>
287 </Pressable>289 </Pressable>
288 );290 );
apps/mobile/src/features/review/nativeReviewDiffAdapter.test.ts+19-2
...@@ -97,6 +97,19 @@ function appTheme(themeId: MobileThemeId, appearance: MobileThemeAppearance) {...@@ -97,6 +97,19 @@ function appTheme(themeId: MobileThemeId, appearance: MobileThemeAppearance) {
97 : getMobileThemeVariables(themeId, appearance);97 : getMobileThemeVariables(themeId, appearance);
98}98}
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
100describe("getCachedNativeReviewDiffData", () => {113describe("getCachedNativeReviewDiffData", () => {
101 it.each([true, false])(114 it.each([true, false])(
102 "preserves available diff rows before a notice (has excerpt: %s)",115 "preserves available diff rows before a notice (has excerpt: %s)",
...@@ -251,6 +264,10 @@ describe("createNativeReviewDiffTheme", () => {...@@ -251,6 +264,10 @@ describe("createNativeReviewDiffTheme", () => {
251 for (const color of Object.values(theme)) {264 for (const color of Object.values(theme)) {
252 expect(color, `${themeId}/${appearance}`).toMatch(/^#[\da-f]{6}$/i);265 expect(color, `${themeId}/${appearance}`).toMatch(/^#[\da-f]{6}$/i);
253 }266 }
267 expect(
268 contrastRatio(theme.hunkText, theme.hunkBackground),
269 `${themeId}/${appearance} hunk text`,
270 ).toBeGreaterThanOrEqual(4.5);
254 }271 }
255 }272 }
256 });273 });
...@@ -261,7 +278,7 @@ describe("createNativeReviewDiffTheme", () => {...@@ -261,7 +278,7 @@ describe("createNativeReviewDiffTheme", () => {
261 const variables = {278 const variables = {
262 ...appTheme("material-you", appearance),279 ...appTheme("material-you", appearance),
263 "--color-screen": "#101214FF",280 "--color-screen": "#101214FF",
264 "--color-sheet": "#20222480",281 "--color-md-code-bg": "#20222480",
265 "--color-md-code-text": "#E3E2E6FF",282 "--color-md-code-text": "#E3E2E6FF",
266 "--color-foreground-muted": "#C7C5D080",283 "--color-foreground-muted": "#C7C5D080",
267 "--color-border": "#44464F80",284 "--color-border": "#44464F80",
...@@ -273,7 +290,7 @@ describe("createNativeReviewDiffTheme", () => {...@@ -273,7 +290,7 @@ describe("createNativeReviewDiffTheme", () => {
273 expect(theme.text).toBe("#e3e2e6");290 expect(theme.text).toBe("#e3e2e6");
274 expect(theme.mutedText).toBe("#707076");291 expect(theme.mutedText).toBe("#707076");
275 expect(theme.border).toBe("#2e3036");292 expect(theme.border).toBe("#2e3036");
276 expect(theme.hunkText).toBe("#a8c7fa");293 expect(contrastRatio(theme.hunkText, theme.hunkBackground)).toBeGreaterThanOrEqual(4.5);
277 for (const color of Object.values(theme)) {294 for (const color of Object.values(theme)) {
278 expect(color).toMatch(/^#[\da-f]{6}$/i);295 expect(color).toMatch(/^#[\da-f]{6}$/i);
279 }296 }
apps/mobile/src/features/review/nativeReviewDiffAdapter.ts+4-7
...@@ -177,20 +177,19 @@ export function createNativeReviewDiffTheme(...@@ -177,20 +177,19 @@ export function createNativeReviewDiffTheme(
177 appTheme["--color-screen"],177 appTheme["--color-screen"],
178 scheme === "dark" ? "#000000" : "#ffffff",178 scheme === "dark" ? "#000000" : "#ffffff",
179 );179 );
180 const background = opaqueNativeHexColor(appTheme["--color-sheet"], screen);180 const background = opaqueNativeHexColor(appTheme["--color-md-code-bg"], screen);
181 const nativeColor = (color: string) => opaqueNativeHexColor(color, background);181 const nativeColor = (color: string) => opaqueNativeHexColor(color, background);
182182
183 if (scheme === "dark") {183 if (scheme === "dark") {
184 return {184 return {
185 // Match the app surface (--color-sheet) so code views blend with the rest of185 // Code surfaces share the desktop palette rather than the sheet behind them.
186 // the app instead of using a distinct code-editor background.
187 background,186 background,
188 text: nativeColor(appTheme["--color-md-code-text"]),187 text: nativeColor(appTheme["--color-md-code-text"]),
189 mutedText: nativeColor(appTheme["--color-foreground-muted"]),188 mutedText: nativeColor(appTheme["--color-foreground-muted"]),
190 headerBackground: background,189 headerBackground: background,
191 border: nativeColor(appTheme["--color-border"]),190 border: nativeColor(appTheme["--color-border"]),
192 hunkBackground: nativeColor(appTheme["--color-subtle-strong"]),191 hunkBackground: nativeColor(appTheme["--color-subtle-strong"]),
193 hunkText: nativeColor(appTheme["--color-primary"]),192 hunkText: nativeColor(appTheme["--color-foreground"]),
194 addBackground: "#0d2f28",193 addBackground: "#0d2f28",
195 deleteBackground: "#391415",194 deleteBackground: "#391415",
196 addBar: "#00cab1",195 addBar: "#00cab1",
...@@ -201,15 +200,13 @@ export function createNativeReviewDiffTheme(...@@ -201,15 +200,13 @@ export function createNativeReviewDiffTheme(
201 }200 }
202201
203 return {202 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.
206 background,203 background,
207 text: nativeColor(appTheme["--color-md-code-text"]),204 text: nativeColor(appTheme["--color-md-code-text"]),
208 mutedText: nativeColor(appTheme["--color-foreground-muted"]),205 mutedText: nativeColor(appTheme["--color-foreground-muted"]),
209 headerBackground: background,206 headerBackground: background,
210 border: nativeColor(appTheme["--color-border"]),207 border: nativeColor(appTheme["--color-border"]),
211 hunkBackground: nativeColor(appTheme["--color-subtle-strong"]),208 hunkBackground: nativeColor(appTheme["--color-subtle-strong"]),
212 hunkText: nativeColor(appTheme["--color-primary"]),209 hunkText: nativeColor(appTheme["--color-foreground"]),
213 addBackground: "#e5f8f5",210 addBackground: "#e5f8f5",
214 deleteBackground: "#ffe6e7",211 deleteBackground: "#ffe6e7",
215 addBar: "#00cab1",212 addBar: "#00cab1",
apps/mobile/src/features/settings/SettingsOpenSourceLicensesRouteScreen.tsx+4-4
...@@ -44,7 +44,7 @@ function LicenseRow(props: {...@@ -44,7 +44,7 @@ function LicenseRow(props: {
44 accessibilityLabel={`${props.entry.name}, ${props.entry.license}`}44 accessibilityLabel={`${props.entry.name}, ${props.entry.license}`}
45 accessibilityRole="button"45 accessibilityRole="button"
46 onPress={props.onPress}46 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"
48 >48 >
49 <View className="flex-row items-start gap-3">49 <View className="flex-row items-start gap-3">
50 <View className="min-w-0 flex-1 gap-1">50 <View className="min-w-0 flex-1 gap-1">
...@@ -235,11 +235,11 @@ export function SettingsOpenSourceLicenseRouteScreen({ route }: LicenseDetailPro...@@ -235,11 +235,11 @@ export function SettingsOpenSourceLicenseRouteScreen({ route }: LicenseDetailPro
235 onPress={() => void Linking.openURL(sourceUrl)}235 onPress={() => void Linking.openURL(sourceUrl)}
236 className="min-h-12 flex-row items-center gap-2 self-start py-2 active:opacity-60"236 className="min-h-12 flex-row items-center gap-2 self-start py-2 active:opacity-60"
237 >237 >
238 <Text className="font-t3-medium text-primary">Project source</Text>238 <Text className="font-t3-medium text-primary-text">Project source</Text>
239 <SymbolView239 <SymbolView
240 name="arrow.up.right"240 name="arrow.up.right"
241 size={16}241 size={16}
242 tintColorClassName={"accent-primary"}242 tintColorClassName={"accent-primary-text"}
243 type="monochrome"243 type="monochrome"
244 weight="semibold"244 weight="semibold"
245 />245 />
...@@ -247,7 +247,7 @@ export function SettingsOpenSourceLicenseRouteScreen({ route }: LicenseDetailPro...@@ -247,7 +247,7 @@ export function SettingsOpenSourceLicenseRouteScreen({ route }: LicenseDetailPro
247 ) : null}247 ) : null}
248 </View>248 </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">
251 <Text selectable className="font-mono text-base leading-normal text-foreground">251 <Text selectable className="font-mono text-base leading-normal text-foreground">
252 {entry.noticeText}252 {entry.noticeText}
253 </Text>253 </Text>
apps/mobile/src/features/settings/appearance/sections/ThemeAppearanceSection.tsx+4-2
...@@ -151,7 +151,7 @@ function ThemeCard(props: {...@@ -151,7 +151,7 @@ function ThemeCard(props: {
151 );151 );
152152
153 return (153 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">
155 <Pressable155 <Pressable
156 accessibilityHint="Sets both light and dark appearances"156 accessibilityHint="Sets both light and dark appearances"
157 accessibilityLabel={`${props.label} theme`}157 accessibilityLabel={`${props.label} theme`}
...@@ -259,7 +259,9 @@ function ModeCard(props: {...@@ -259,7 +259,9 @@ function ModeCard(props: {
259 accessibilityState={{ checked: props.selected, disabled: props.disabled }}259 accessibilityState={{ checked: props.selected, disabled: props.disabled }}
260 className={cn(260 className={cn(
261 "min-w-0 flex-1 gap-2 rounded-[24px] p-2 active:scale-[0.97]",261 "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",
263 )}265 )}
264 disabled={props.disabled}266 disabled={props.disabled}
265 onPress={props.onPress}267 onPress={props.onPress}
apps/mobile/src/features/settings/components/AutoSettleDaysField.ios.tsx+3-3
...@@ -44,7 +44,7 @@ export function AutoSettleDaysField(props: AutoSettleDaysFieldProps) {...@@ -44,7 +44,7 @@ export function AutoSettleDaysField(props: AutoSettleDaysFieldProps) {
44 disabled(props.disabled),44 disabled(props.disabled),
45 accessibilityLabel(`Days before auto-settle: ${props.value}`),45 accessibilityLabel(`Days before auto-settle: ${props.value}`),
46 frame({ minWidth: 64, minHeight: 44 }),46 frame({ minWidth: 64, minHeight: 44 }),
47 foregroundStyle(colors["--color-primary"]),47 foregroundStyle(colors["--color-primary-text"]),
48 font({ size: appearance.baseFontSize }),48 font({ size: appearance.baseFontSize }),
49 ]}49 ]}
50 >50 >
...@@ -78,7 +78,7 @@ export function AutoSettleDaysField(props: AutoSettleDaysFieldProps) {...@@ -78,7 +78,7 @@ export function AutoSettleDaysField(props: AutoSettleDaysFieldProps) {
78 <Button78 <Button
79 label="Cancel"79 label="Cancel"
80 onPress={() => setOpen(false)}80 onPress={() => setOpen(false)}
81 modifiers={[foregroundStyle(colors["--color-primary"])]}81 modifiers={[foregroundStyle(colors["--color-primary-text"])]}
82 />82 />
83 <Button83 <Button
84 label="Done"84 label="Done"
...@@ -86,7 +86,7 @@ export function AutoSettleDaysField(props: AutoSettleDaysFieldProps) {...@@ -86,7 +86,7 @@ export function AutoSettleDaysField(props: AutoSettleDaysFieldProps) {
86 setOpen(false);86 setOpen(false);
87 if (!props.disabled && draft !== props.value) props.onValueChange(draft);87 if (!props.disabled && draft !== props.value) props.onValueChange(draft);
88 }}88 }}
89 modifiers={[foregroundStyle(colors["--color-primary"])]}89 modifiers={[foregroundStyle(colors["--color-primary-text"])]}
90 />90 />
91 </HStack>91 </HStack>
92 </VStack>92 </VStack>
apps/mobile/src/features/settings/components/SettingsActionRow.tsx+1
...@@ -31,6 +31,7 @@ export function SettingsActionRow(props: {...@@ -31,6 +31,7 @@ export function SettingsActionRow(props: {
31 if (Platform.OS === "android") {31 if (Platform.OS === "android") {
32 return (32 return (
33 <MaterialListRow33 <MaterialListRow
34 className="bg-grouped-card"
34 title={props.label}35 title={props.label}
35 titleClassName={textClassName}36 titleClassName={textClassName}
36 leading={icon}37 leading={icon}
apps/mobile/src/features/settings/components/SettingsProjectOverridesSection.tsx+1-1
...@@ -23,7 +23,7 @@ export function SettingsProjectOverridesSection(props: {...@@ -23,7 +23,7 @@ export function SettingsProjectOverridesSection(props: {
23 onPress={props.onClear}23 onPress={props.onClear}
24 className="px-2 py-2 active:opacity-70"24 className="px-2 py-2 active:opacity-70"
25 >25 >
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>
27 </Pressable>27 </Pressable>
28 ) : null}28 ) : null}
29 </View>29 </View>
apps/mobile/src/features/settings/components/SettingsRow.tsx+1
...@@ -24,6 +24,7 @@ export function SettingsRow(props: {...@@ -24,6 +24,7 @@ export function SettingsRow(props: {
24 if (Platform.OS === "android") {24 if (Platform.OS === "android") {
25 return (25 return (
26 <MaterialListRow26 <MaterialListRow
27 className="bg-grouped-card"
27 title={props.label}28 title={props.label}
28 subtitle={props.valuePosition === "trailing" ? undefined : props.value}29 subtitle={props.valuePosition === "trailing" ? undefined : props.value}
29 accessibilityLabel={[props.label, props.value].filter(Boolean).join(", ")}30 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: {...@@ -15,7 +15,7 @@ export function SettingsSection(props: {
15 <Text15 <Text
16 className={16 className={
17 Platform.OS === "android"17 Platform.OS === "android"
18 ? "px-4 text-sm font-t3-medium text-primary"18 ? "px-4 text-sm font-t3-medium text-primary-text"
19 : "px-2 text-sm font-t3-medium text-foreground-muted"19 : "px-2 text-sm font-t3-medium text-foreground-muted"
20 }20 }
21 >21 >
...@@ -27,8 +27,8 @@ export function SettingsSection(props: {...@@ -27,8 +27,8 @@ export function SettingsSection(props: {
27 <View27 <View
28 className={28 className={
29 Platform.OS === "android"29 Platform.OS === "android"
30 ? "overflow-hidden rounded-[28px] bg-card"30 ? "overflow-hidden rounded-[28px] bg-grouped-card"
31 : "overflow-hidden rounded-[24px] border-continuous bg-card"31 : "overflow-hidden rounded-[24px] border-continuous bg-grouped-card"
32 }32 }
33 >33 >
34 {props.children}34 {props.children}
apps/mobile/src/features/terminal/terminalTheme.test.ts+10-10
...@@ -6,20 +6,20 @@ import { themeColorToNativeColor } from "../../lib/mobileTheme";...@@ -6,20 +6,20 @@ import { themeColorToNativeColor } from "../../lib/mobileTheme";
6import { buildGhosttyThemeConfig, getMobileTerminalTheme } from "./terminalTheme";6import { buildGhosttyThemeConfig, getMobileTerminalTheme } from "./terminalTheme";
77
8describe("getMobileTerminalTheme", () => {8describe("getMobileTerminalTheme", () => {
9 it("preserves the default light terminal palette", () => {9 it("uses the shared default light terminal colors", () => {
10 expect(getMobileTerminalTheme("t3-code", "light")).toMatchObject({10 expect(getMobileTerminalTheme("t3-code", "light")).toMatchObject({
11 background: "#f2f2f7",11 background: "#fcfcfc",
12 foreground: "#6C6C71",12 foreground: "#27272a",
13 cursorForeground: "#009fff",13 cursorForeground: "#26384e",
14 cursorBackground: "#f2f2f7",14 cursorBackground: "#fcfcfc",
15 });15 });
16 });16 });
1717
18 it("preserves the default dark terminal palette", () => {18 it("uses the shared default dark terminal colors", () => {
19 expect(getMobileTerminalTheme("t3-code", "dark")).toMatchObject({19 expect(getMobileTerminalTheme("t3-code", "dark")).toMatchObject({
20 background: "#0a0a0a",20 background: "#0a0a0a",
21 foreground: "#adadb1",21 foreground: "#f5f5f5",
22 cursorForeground: "#009fff",22 cursorForeground: "#b4cbff",
23 cursorBackground: "#0a0a0a",23 cursorBackground: "#0a0a0a",
24 });24 });
25 });25 });
...@@ -48,8 +48,8 @@ describe("buildGhosttyThemeConfig", () => {...@@ -48,8 +48,8 @@ describe("buildGhosttyThemeConfig", () => {
48 const config = buildGhosttyThemeConfig(getMobileTerminalTheme("t3-code", "dark"));48 const config = buildGhosttyThemeConfig(getMobileTerminalTheme("t3-code", "dark"));
4949
50 expect(config).toContain("background = #0a0a0a");50 expect(config).toContain("background = #0a0a0a");
51 expect(config).toContain("foreground = #adadb1");51 expect(config).toContain("foreground = #f5f5f5");
52 expect(config).toContain("cursor-color = #009fff");52 expect(config).toContain("cursor-color = #b4cbff");
53 expect(config).toContain("palette = 0=#141415");53 expect(config).toContain("palette = 0=#141415");
54 expect(config).toContain("palette = 15=#c6c6c8");54 expect(config).toContain("palette = 15=#c6c6c8");
55 expect(config.endsWith("\n")).toBe(true);55 expect(config.endsWith("\n")).toBe(true);
apps/mobile/src/features/terminal/terminalTheme.ts+6-13
...@@ -1,10 +1,5 @@...@@ -1,10 +1,5 @@
1import {1import {
2 BUILT_IN_THEMES,2 getMobileThemeColors,
3 T3_CHAT_THEME,
4 getThemeColorsForAppearance,
5} from "@t3tools/shared/themePalettes";
6
7import {
8 getMobileThemeVariables,3 getMobileThemeVariables,
9 themeColorToNativeColor,4 themeColorToNativeColor,
10 type MobileThemeId,5 type MobileThemeId,
...@@ -44,12 +39,12 @@ type TerminalPalette = readonly [...@@ -44,12 +39,12 @@ type TerminalPalette = readonly [
4439
45const PIERRE_LIGHT_THEME: TerminalTheme = {40const PIERRE_LIGHT_THEME: TerminalTheme = {
46 // Pierre terminal palette with the app's shared screen background.41 // Pierre terminal palette with the app's shared screen background.
47 background: "#f2f2f7",42 background: "#fcfcfc",
48 foreground: "#6C6C71",43 foreground: "#6C6C71",
49 mutedForeground: "#8E8E95",44 mutedForeground: "#8E8E95",
50 border: "#eeeeef",45 border: "#eeeeef",
51 cursorForeground: "#009fff",46 cursorForeground: "#009fff",
52 cursorBackground: "#f2f2f7",47 cursorBackground: "#fcfcfc",
53 palette: [48 palette: [
54 "#1F1F21",49 "#1F1F21",
55 "#ff2e3f",50 "#ff2e3f",
...@@ -107,11 +102,9 @@ export function getMobileTerminalTheme(...@@ -107,11 +102,9 @@ export function getMobileTerminalTheme(
107 scheme: TerminalAppearanceScheme,102 scheme: TerminalAppearanceScheme,
108): TerminalTheme {103): TerminalTheme {
109 const base = getPierreTerminalTheme(scheme);104 const base = getPierreTerminalTheme(scheme);
110 if (themeId === "t3-code" || themeId === "material-you") return base;105 const paletteId = themeId === "material-you" ? "t3-code" : themeId;
111106 const palette = getMobileThemeColors(paletteId, scheme);
112 const theme = BUILT_IN_THEMES.find((candidate) => candidate.id === themeId) ?? T3_CHAT_THEME;107 const colors = getMobileThemeVariables(paletteId, scheme);
113 const palette = getThemeColorsForAppearance(theme, scheme) ?? theme.colors;
114 const colors = getMobileThemeVariables(themeId, scheme);
115 const background = themeColorToNativeColor(palette.terminalBackground);108 const background = themeColorToNativeColor(palette.terminalBackground);
116 return {109 return {
117 ...base,110 ...base,
apps/mobile/src/features/threads/CustomSnoozeSheet.shared.tsx+1-1
...@@ -31,7 +31,7 @@ export function CustomSnoozeSheet(props: {...@@ -31,7 +31,7 @@ export function CustomSnoozeSheet(props: {
31 return (31 return (
32 <Modal visible transparent animationType="fade" onRequestClose={props.onClose}>32 <Modal visible transparent animationType="fade" onRequestClose={props.onClose}>
33 <KeyboardAvoidingView33 <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"
35 behavior={Platform.OS === "ios" ? "padding" : undefined}35 behavior={Platform.OS === "ios" ? "padding" : undefined}
36 >36 >
37 <ScrollView37 <ScrollView
apps/mobile/src/features/threads/NewTaskContextPickerScreens.tsx+4-4
...@@ -66,7 +66,7 @@ function SelectionRow(props: {...@@ -66,7 +66,7 @@ function SelectionRow(props: {
66 }66 }
67 trailing={67 trailing={
68 props.selected ? (68 props.selected ? (
69 <SymbolView name="checkmark" size={20} tintColorClassName="accent-primary" />69 <SymbolView name="checkmark" size={20} tintColorClassName="accent-focus" />
70 ) : null70 ) : null
71 }71 }
72 accessibilityRole="radio"72 accessibilityRole="radio"
...@@ -482,9 +482,9 @@ export function NewTaskBranchPickerRouteScreen() {...@@ -482,9 +482,9 @@ export function NewTaskBranchPickerRouteScreen() {
482 autoCorrect={false}482 autoCorrect={false}
483 accessibilityLabel="Find a branch"483 accessibilityLabel="Find a branch"
484 className="h-12 rounded-full border border-input-border bg-input px-4 font-sans text-base text-foreground"484 className="h-12 rounded-full border border-input-border bg-input px-4 font-sans text-base text-foreground"
485 selectionColorClassName="accent-primary/32"485 selectionColorClassName="accent-focus/32"
486 cursorColorClassName="accent-primary"486 cursorColorClassName="accent-focus"
487 selectionHandleColorClassName="accent-primary"487 selectionHandleColorClassName="accent-focus"
488 onChangeText={flow.setBranchQuery}488 onChangeText={flow.setBranchQuery}
489 placeholder="Find a branch"489 placeholder="Find a branch"
490 placeholderTextColorClassName="accent-placeholder"490 placeholderTextColorClassName="accent-placeholder"
apps/mobile/src/features/threads/ThreadArrangementSheet.tsx+1-1
...@@ -379,7 +379,7 @@ export function ThreadArrangementSheet(props: { onClose: () => void }) {...@@ -379,7 +379,7 @@ export function ThreadArrangementSheet(props: { onClose: () => void }) {
379 onPress={props.onClose}379 onPress={props.onClose}
380 className="min-h-11 justify-center px-3"380 className="min-h-11 justify-center px-3"
381 >381 >
382 <Text className="text-base text-primary">Done</Text>382 <Text className="text-base text-primary-text">Done</Text>
383 </Pressable>383 </Pressable>
384 </View>384 </View>
385 <Text className="px-5 pb-3 text-sm text-foreground-muted">385 <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: {...@@ -244,12 +244,8 @@ export function ComposerSurface(props: {
244 >244 >
245 <AnimatedGlassSurface245 <AnimatedGlassSurface
246 chrome="none"246 chrome="none"
247 fallbackColor={247 fallbackColor={colors["--color-composer-surface"]}
248 Platform.OS === "android" ? colors["--color-composer-surface"] : colors["--color-card"]248 fallbackClassName="border border-composer-border"
249 }
250 fallbackClassName={
251 Platform.OS === "android" ? "border border-composer-border" : "border border-border"
252 }
253 glassEffectStyle="regular"249 glassEffectStyle="regular"
254 // The composer is a passive material containing interactive controls.250 // The composer is a passive material containing interactive controls.
255 // Keep native glass out of the interactive content's layout path.251 // 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: {...@@ -13,14 +13,14 @@ export function ThreadCreationFailedCard(props: {
13 readonly onEditTask: () => void;13 readonly onEditTask: () => void;
14}) {14}) {
15 return (15 return (
16 <View className="gap-2.5 rounded-[20px] border border-adaptive-neutral-200-white-a6 bg-adaptive-neutral-100-900 p-4">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-adaptive-rose-700-300">17 <Text className="font-t3-bold text-2xs uppercase tracking-[1.1px] text-danger-foreground">
18 Could not start task18 Could not start task
19 </Text>19 </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">
21 {props.reason}21 {props.reason}
22 </Text>22 </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">
24 Your prompt was kept in the project draft.24 Your prompt was kept in the project draft.
25 </Text>25 </Text>
26 <View className="flex-row">26 <View className="flex-row">
apps/mobile/src/features/threads/ThreadFeed.tsx+9-9
...@@ -888,7 +888,7 @@ function MarkdownCodeBlock(props: {...@@ -888,7 +888,7 @@ function MarkdownCodeBlock(props: {
888 >888 >
889 <NativeText889 <NativeText
890 selectable890 selectable
891 selectionColorClassName={Platform.OS === "android" ? "accent-primary/32" : undefined}891 selectionColorClassName={Platform.OS === "android" ? "accent-focus/32" : undefined}
892 className="font-mono"892 className="font-mono"
893 style={{893 style={{
894 color: props.textColor,894 color: props.textColor,
...@@ -1400,7 +1400,7 @@ function renderFeedEntry(...@@ -1400,7 +1400,7 @@ function renderFeedEntry(
1400 accessibilityState={{ expanded: entry.expanded }}1400 accessibilityState={{ expanded: entry.expanded }}
1401 onPress={() => props.onToggleTurnFold(entry.turnId)}1401 onPress={() => props.onToggleTurnFold(entry.turnId)}
1402 hitSlop={4}1402 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"
1404 style={{1404 style={{
1405 minHeight: Math.max(TURN_FOLD_HEIGHT - 3.5, props.workRowSizing.estimatedRowHeight),1405 minHeight: Math.max(TURN_FOLD_HEIGHT - 3.5, props.workRowSizing.estimatedRowHeight),
1406 }}1406 }}
...@@ -1467,7 +1467,7 @@ function renderFeedEntry(...@@ -1467,7 +1467,7 @@ function renderFeedEntry(
1467 accessibilityLabel={label}1467 accessibilityLabel={label}
1468 className="mb-3 flex-row items-center gap-3 px-1 py-1"1468 className="mb-3 flex-row items-center gap-3 px-1 py-1"
1469 >1469 >
1470 <View className="h-px flex-1 bg-adaptive-neutral-200-a80-white-a8" />1470 <View className="h-px flex-1 bg-subtle" />
1471 <View className="shrink-0 flex-row items-center gap-1.5">1471 <View className="shrink-0 flex-row items-center gap-1.5">
1472 <SymbolView1472 <SymbolView
1473 name="arrow.down.right.and.arrow.up.left"1473 name="arrow.down.right.and.arrow.up.left"
...@@ -1477,7 +1477,7 @@ function renderFeedEntry(...@@ -1477,7 +1477,7 @@ function renderFeedEntry(
1477 />1477 />
1478 <Text className="font-t3-medium text-xs text-foreground-muted">{label}</Text>1478 <Text className="font-t3-medium text-xs text-foreground-muted">{label}</Text>
1479 </View>1479 </View>
1480 <View className="h-px flex-1 bg-adaptive-neutral-200-a80-white-a8" />1480 <View className="h-px flex-1 bg-subtle" />
1481 </View>1481 </View>
1482 );1482 );
1483 }1483 }
...@@ -1599,8 +1599,8 @@ function renderFeedEntry(...@@ -1599,8 +1599,8 @@ function renderFeedEntry(
1599 mimeType={attachment.mimeType}1599 mimeType={attachment.mimeType}
1600 className={1600 className={
1601 inlineAttachmentIds.size1601 inlineAttachmentIds.size
1602 ? "h-24 w-24 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-white/15"1603 : "aspect-[1.3] w-full rounded-[14px] bg-user-bubble-foreground/15"
1604 }1604 }
1605 onPressPreview={props.onPressPreview}1605 onPressPreview={props.onPressPreview}
1606 />1606 />
...@@ -1636,7 +1636,7 @@ function renderFeedEntry(...@@ -1636,7 +1636,7 @@ function renderFeedEntry(
1636 ) : null}1636 ) : null}
1637 </View>1637 </View>
1638 <View className="mt-1 flex-row items-center justify-end gap-1 pr-0.5">1638 <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">
1640 {entry.pendingMessage && !entry.acknowledged ? "Pending" : timestampLabel}1640 {entry.pendingMessage && !entry.acknowledged ? "Pending" : timestampLabel}
1641 </Text>1641 </Text>
1642 {entry.pendingMessage &&1642 {entry.pendingMessage &&
...@@ -1715,7 +1715,7 @@ function renderFeedEntry(...@@ -1715,7 +1715,7 @@ function renderFeedEntry(
1715 attachmentId={attachment.id}1715 attachmentId={attachment.id}
1716 name={attachment.name}1716 name={attachment.name}
1717 mimeType={attachment.mimeType}1717 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"
1719 onPressPreview={props.onPressPreview}1719 onPressPreview={props.onPressPreview}
1720 />1720 />
1721 ) : isFileAttachment(attachment) ? (1721 ) : isFileAttachment(attachment) ? (
...@@ -1739,7 +1739,7 @@ function renderFeedEntry(...@@ -1739,7 +1739,7 @@ function renderFeedEntry(
1739 buttonSize={28}1739 buttonSize={28}
1740 iconSize={13}1740 iconSize={13}
1741 />1741 />
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">
1743 {timestampLabel}1743 {timestampLabel}
1744 </Text>1744 </Text>
1745 </View>1745 </View>
apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx+17-13
...@@ -152,7 +152,7 @@ function ThreadNavigationSidebarPane(...@@ -152,7 +152,7 @@ function ThreadNavigationSidebarPane(
152 props: ThreadNavigationSidebarProps & { readonly nativeChrome: boolean },152 props: ThreadNavigationSidebarProps & { readonly nativeChrome: boolean },
153) {153) {
154 const { themeVariables: materialTheme } = useAppearancePreferences();154 const { themeVariables: materialTheme } = useAppearancePreferences();
155 const screenColor = materialTheme["--color-screen"];155 const drawerColor = materialTheme["--color-drawer"];
156156
157 const insets = useSafeAreaInsets();157 const insets = useSafeAreaInsets();
158 const projects = useProjects();158 const projects = useProjects();
...@@ -935,7 +935,7 @@ function ThreadNavigationSidebarPane(...@@ -935,7 +935,7 @@ function ThreadNavigationSidebarPane(
935 }),935 }),
936 )}936 )}
937 searchQuery={props.searchQuery}937 searchQuery={props.searchQuery}
938 pane={Platform.OS === "android" ? "screen" : "sidebar"}938 pane="sidebar"
939 selected={939 selected={
940 scopedThreadKey(thread.environmentId, thread.id) === props.selectedThreadKey940 scopedThreadKey(thread.environmentId, thread.id) === props.selectedThreadKey
941 }941 }
...@@ -976,7 +976,7 @@ function ThreadNavigationSidebarPane(...@@ -976,7 +976,7 @@ function ThreadNavigationSidebarPane(
976 disabled={!shelfPreferencesLoaded}976 disabled={!shelfPreferencesLoaded}
977 expanded={item.expanded}977 expanded={item.expanded}
978 onToggle={toggleSnoozedShelf}978 onToggle={toggleSnoozedShelf}
979 pane={Platform.OS === "android" ? "screen" : "sidebar"}979 pane="sidebar"
980 />980 />
981 );981 );
982 case "v2-settled-shelf":982 case "v2-settled-shelf":
...@@ -986,17 +986,21 @@ function ThreadNavigationSidebarPane(...@@ -986,17 +986,21 @@ function ThreadNavigationSidebarPane(
986 disabled={!shelfPreferencesLoaded}986 disabled={!shelfPreferencesLoaded}
987 expanded={item.expanded}987 expanded={item.expanded}
988 onToggle={toggleSettledShelf}988 onToggle={toggleSettledShelf}
989 pane={Platform.OS === "android" ? "screen" : "sidebar"}989 pane="sidebar"
990 />990 />
991 );991 );
992 case "v2-show-more":992 case "v2-show-more":
993 return (993 return (
994 <ThreadListV2ShowMoreRow hiddenCount={item.hiddenCount} onPress={showMoreSettled} />994 <ThreadListV2ShowMoreRow
995 pane="sidebar"
996 hiddenCount={item.hiddenCount}
997 onPress={showMoreSettled}
998 />
995 );999 );
996 case "header":1000 case "header":
997 return (1001 return (
998 <ThreadListGroupHeader1002 <ThreadListGroupHeader
999 variant={Platform.OS === "android" ? "compact" : "sidebar"}1003 variant="sidebar"
1000 collapsed={item.collapsed}1004 collapsed={item.collapsed}
1001 isFirst={item.isFirst}1005 isFirst={item.isFirst}
1002 groupKey={item.group.key}1006 groupKey={item.group.key}
...@@ -1014,7 +1018,7 @@ function ThreadNavigationSidebarPane(...@@ -1014,7 +1018,7 @@ function ThreadNavigationSidebarPane(
1014 case "pending-task":1018 case "pending-task":
1015 return (1019 return (
1016 <PendingTaskListRow1020 <PendingTaskListRow
1017 variant={Platform.OS === "android" ? "compact" : "sidebar"}1021 variant="sidebar"
1018 pendingTask={item.pendingTask}1022 pendingTask={item.pendingTask}
1019 environmentLabel={1023 environmentLabel={
1020 savedConnectionsById[item.pendingTask.environmentId]?.environmentLabel ?? null1024 savedConnectionsById[item.pendingTask.environmentId]?.environmentLabel ?? null
...@@ -1064,7 +1068,7 @@ function ThreadNavigationSidebarPane(...@@ -1064,7 +1068,7 @@ function ThreadNavigationSidebarPane(
1064 case "show-more":1068 case "show-more":
1065 return (1069 return (
1066 <ThreadListShowMoreRow1070 <ThreadListShowMoreRow
1067 variant={Platform.OS === "android" ? "compact" : "sidebar"}1071 variant="sidebar"
1068 hiddenCount={item.hiddenCount}1072 hiddenCount={item.hiddenCount}
1069 canShowLess={item.canShowLess}1073 canShowLess={item.canShowLess}
1070 groupKey={item.groupKey}1074 groupKey={item.groupKey}
...@@ -1168,8 +1172,8 @@ function ThreadNavigationSidebarPane(...@@ -1168,8 +1172,8 @@ function ThreadNavigationSidebarPane(
1168 <Text1172 <Text
1169 className={1173 className={
1170 Platform.OS === "android"1174 Platform.OS === "android"
1171 ? "px-4 py-4 text-center text-sm text-foreground-muted"1175 ? "px-4 py-4 text-center text-sm text-drawer-foreground-muted"
1172 : "px-2 py-4 text-sm text-foreground-muted"1176 : "px-2 py-4 text-sm text-drawer-foreground-muted"
1173 }1177 }
1174 >1178 >
1175 {catalogState.isLoadingConnections1179 {catalogState.isLoadingConnections
...@@ -1277,7 +1281,7 @@ function ThreadNavigationSidebarPane(...@@ -1277,7 +1281,7 @@ function ThreadNavigationSidebarPane(
1277 marginTop: stickyHeaderHeight,1281 marginTop: stickyHeaderHeight,
1278 marginHorizontal: 4,1282 marginHorizontal: 4,
1279 paddingBottom: insets.bottom,1283 paddingBottom: insets.bottom,
1280 backgroundColor: screenColor,1284 backgroundColor: drawerColor,
1281 borderTopLeftRadius: 28,1285 borderTopLeftRadius: 28,
1282 borderTopRightRadius: 28,1286 borderTopRightRadius: 28,
1283 overflow: "hidden",1287 overflow: "hidden",
...@@ -1374,7 +1378,7 @@ function ThreadNavigationSidebarPane(...@@ -1374,7 +1378,7 @@ function ThreadNavigationSidebarPane(
1374 <SymbolView1378 <SymbolView
1375 name="magnifyingglass"1379 name="magnifyingglass"
1376 size={15}1380 size={15}
1377 tintColorClassName="accent-foreground-muted"1381 tintColorClassName="accent-drawer-foreground-muted"
1378 type="monochrome"1382 type="monochrome"
1379 />1383 />
1380 <TextInput1384 <TextInput
...@@ -1390,7 +1394,7 @@ function ThreadNavigationSidebarPane(...@@ -1390,7 +1394,7 @@ function ThreadNavigationSidebarPane(
1390 cursorColorClassName={undefined}1394 cursorColorClassName={undefined}
1391 selectionHandleColorClassName={undefined}1395 selectionHandleColorClassName={undefined}
1392 returnKeyType="search"1396 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"
1394 value={props.searchQuery}1398 value={props.searchQuery}
1395 />1399 />
1396 </View>1400 </View>
apps/mobile/src/features/threads/ThreadSettingsRows.android.tsx+1-1
...@@ -12,7 +12,7 @@ export function ModelRow(props: ModelRowProps) {...@@ -12,7 +12,7 @@ export function ModelRow(props: ModelRowProps) {
12 {...props}12 {...props}
13 labelNumberOfLines={2}13 labelNumberOfLines={2}
14 minimumHeight={56}14 minimumHeight={56}
15 selectedClassName={props.selected ? "bg-thread-selected" : undefined}15 selectedClassName={props.selected ? "bg-secondary" : undefined}
16 leadingSelection={<MaterialRadioIndicator selected={props.selected} />}16 leadingSelection={<MaterialRadioIndicator selected={props.selected} />}
17 />17 />
18 );18 );
apps/mobile/src/features/threads/ThreadSettingsSheet.tsx+3-3
...@@ -896,9 +896,9 @@ function ThreadSettingsMainContent(props: {...@@ -896,9 +896,9 @@ function ThreadSettingsMainContent(props: {
896 onChangeText={session.setSearchQuery}896 onChangeText={session.setSearchQuery}
897 placeholder="Find a model"897 placeholder="Find a model"
898 placeholderTextColorClassName="accent-placeholder"898 placeholderTextColorClassName="accent-placeholder"
899 selectionColorClassName="accent-primary/32"899 selectionColorClassName="accent-focus/32"
900 cursorColorClassName="accent-primary"900 cursorColorClassName="accent-focus"
901 selectionHandleColorClassName="accent-primary"901 selectionHandleColorClassName="accent-focus"
902 value={session.searchQuery}902 value={session.searchQuery}
903 />903 />
904 {session.searchQuery.length > 0 ? (904 {session.searchQuery.length > 0 ? (
apps/mobile/src/features/threads/floating-working-control.tsx+3-3
...@@ -213,7 +213,7 @@ export function FloatingWorkingControl(props: {...@@ -213,7 +213,7 @@ export function FloatingWorkingControl(props: {
213 <View pointerEvents="box-none" className="flex-row items-center gap-4">213 <View pointerEvents="box-none" className="flex-row items-center gap-4">
214 <Animated.View214 <Animated.View
215 pointerEvents={capsuleInteractive ? "box-none" : "none"}215 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"
217 style={capsuleStyle}217 style={capsuleStyle}
218 >218 >
219 {capsuleContent}219 {capsuleContent}
...@@ -228,7 +228,7 @@ export function FloatingWorkingControl(props: {...@@ -228,7 +228,7 @@ export function FloatingWorkingControl(props: {
228 <ControlPill228 <ControlPill
229 accessibilityLabel="Scroll to end"229 accessibilityLabel="Scroll to end"
230 activateOnPressIn230 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"
232 disabled={!props.showScrollToEnd}232 disabled={!props.showScrollToEnd}
233 icon={{ ios: "chevron.down", android: "keyboard_arrow_down" }}233 icon={{ ios: "chevron.down", android: "keyboard_arrow_down" }}
234 onPress={props.onScrollToEnd}234 onPress={props.onScrollToEnd}
...@@ -248,7 +248,7 @@ export function FloatingWorkingControl(props: {...@@ -248,7 +248,7 @@ export function FloatingWorkingControl(props: {
248 <ControlPill248 <ControlPill
249 accessibilityLabel="Scroll to end"249 accessibilityLabel="Scroll to end"
250 activateOnPressIn250 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"
252 icon={{ ios: "chevron.down", android: "keyboard_arrow_down" }}252 icon={{ ios: "chevron.down", android: "keyboard_arrow_down" }}
253 onPress={props.onScrollToEnd}253 onPress={props.onScrollToEnd}
254 />254 />
apps/mobile/src/features/threads/git/GitCommitSheet.tsx+8-6
...@@ -164,7 +164,7 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {...@@ -164,7 +164,7 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {
164 <Text164 <Text
165 className={165 className={
166 Platform.OS === "android"166 Platform.OS === "android"
167 ? "text-primary text-sm font-t3-medium"167 ? "text-primary-text text-sm font-t3-medium"
168 : "text-foreground text-2xs font-t3-bold uppercase"168 : "text-foreground text-2xs font-t3-bold uppercase"
169 }169 }
170 >170 >
...@@ -183,7 +183,7 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {...@@ -183,7 +183,7 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {
183 <Text183 <Text
184 className={184 className={
185 Platform.OS === "android"185 Platform.OS === "android"
186 ? "text-primary text-sm font-t3-medium"186 ? "text-primary-text text-sm font-t3-medium"
187 : "text-foreground text-2xs font-t3-bold uppercase"187 : "text-foreground text-2xs font-t3-bold uppercase"
188 }188 }
189 >189 >
...@@ -204,10 +204,12 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {...@@ -204,10 +204,12 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {
204 <Text className="text-foreground flex-1 text-sm font-medium" numberOfLines={1}>204 <Text className="text-foreground flex-1 text-sm font-medium" numberOfLines={1}>
205 {file.path}205 {file.path}
206 </Text>206 </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">
208 +{file.insertions}208 +{file.insertions}
209 </Text>209 </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>
211 </View>213 </View>
212 ))}214 ))}
213 {selectedFiles.length > selectedFilePreview.length ? (215 {selectedFiles.length > selectedFilePreview.length ? (
...@@ -286,10 +288,10 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {...@@ -286,10 +288,10 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {
286 ) : null}288 ) : null}
287 </View>289 </View>
288 <View className="items-end gap-1">290 <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">
290 +{file.insertions}292 +{file.insertions}
291 </Text>293 </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">
293 -{file.deletions}295 -{file.deletions}
294 </Text>296 </Text>
295 </View>297 </View>
apps/mobile/src/features/threads/sidebar-navigation-shell.tsx+1-1
...@@ -52,7 +52,7 @@ const SidebarStack = createNativeStackNavigator();...@@ -52,7 +52,7 @@ const SidebarStack = createNativeStackNavigator();
52 * navigation hooks used for header configuration inside the pane.52 * navigation hooks used for header configuration inside the pane.
53 */53 */
54export function SidebarNavigationShell(props: { readonly children: ReactNode }) {54export function SidebarNavigationShell(props: { readonly children: ReactNode }) {
55 const navigationTheme = useMobileNavigationTheme();55 const navigationTheme = useMobileNavigationTheme("sidebar");
5656
57 return (57 return (
58 <NavigationIndependentTree>58 <NavigationIndependentTree>
apps/mobile/src/features/threads/thread-list-items.tsx+76-95
...@@ -157,7 +157,7 @@ export const ThreadListGroupHeader = memo(function ThreadListGroupHeader(props:...@@ -157,7 +157,7 @@ export const ThreadListGroupHeader = memo(function ThreadListGroupHeader(props:
157 className={157 className={
158 compact158 compact
159 ? "flex-shrink text-base font-t3-bold tracking-[0.2px] text-foreground-muted"159 ? "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"
161 }161 }
162 numberOfLines={1}162 numberOfLines={1}
163 >163 >
...@@ -167,7 +167,7 @@ export const ThreadListGroupHeader = memo(function ThreadListGroupHeader(props:...@@ -167,7 +167,7 @@ export const ThreadListGroupHeader = memo(function ThreadListGroupHeader(props:
167 className={167 className={
168 compact168 compact
169 ? "flex-1 text-sm font-t3-medium text-foreground-tertiary"169 ? "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"
171 }171 }
172 >172 >
173 {props.threadCount}173 {props.threadCount}
...@@ -241,7 +241,7 @@ export const ThreadListShowMoreRow = memo(function ThreadListShowMoreRow(props:...@@ -241,7 +241,7 @@ export const ThreadListShowMoreRow = memo(function ThreadListShowMoreRow(props:
241 className={241 className={
242 compact242 compact
243 ? "text-sm font-t3-medium text-foreground-muted"243 ? "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"
245 }245 }
246 >246 >
247 {label}247 {label}
...@@ -320,7 +320,14 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {...@@ -320,7 +320,14 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {
320 </View>320 </View>
321 ) : (321 ) : (
322 <View className="rounded-full bg-subtle px-1.5 py-0.5">322 <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>
324 </View>331 </View>
325 );332 );
326333
...@@ -330,21 +337,21 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {...@@ -330,21 +337,21 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {
330 <SymbolView337 <SymbolView
331 name={isDraft ? "square.and.pencil" : "tray.and.arrow.up"}338 name={isDraft ? "square.and.pencil" : "tray.and.arrow.up"}
332 size={10}339 size={10}
333 tintColorClassName={compact ? "accent-icon-subtle" : "accent-foreground-muted"}340 tintColorClassName={compact ? "accent-icon-subtle" : "accent-drawer-foreground-muted"}
334 type="monochrome"341 type="monochrome"
335 />342 />
336 {props.environmentLabel && props.environmentMachine ? (343 {props.environmentLabel && props.environmentMachine ? (
337 <EnvironmentMachineSymbol344 <EnvironmentMachineSymbol
338 kind={props.environmentMachine}345 kind={props.environmentMachine}
339 size={compact ? 12 : 10}346 size={compact ? 12 : 10}
340 tintColorClassName={compact ? "accent-icon-subtle" : "accent-foreground-muted"}347 tintColorClassName={compact ? "accent-icon-subtle" : "accent-drawer-foreground-muted"}
341 />348 />
342 ) : null}349 ) : null}
343 <Text350 <Text
344 className={351 className={
345 compact352 compact
346 ? "shrink text-sm text-foreground-muted"353 ? "shrink text-sm text-foreground-muted"
347 : "shrink text-xs text-foreground-muted"354 : "shrink text-xs text-drawer-foreground-muted"
348 }355 }
349 numberOfLines={1}356 numberOfLines={1}
350 >357 >
...@@ -369,13 +376,26 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {...@@ -369,13 +376,26 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {
369 <View className="pr-[18px] pt-[10px]" style={{ paddingLeft: THREAD_LIST_COMPACT_INSET }}>376 <View className="pr-[18px] pt-[10px]" style={{ paddingLeft: THREAD_LIST_COMPACT_INSET }}>
370 <View className={cn("gap-[3px] pb-[10px]", !props.isLast && "border-b border-separator")}>377 <View className={cn("gap-[3px] pb-[10px]", !props.isLast && "border-b border-separator")}>
371 <View className="flex-row items-center justify-between gap-2">378 <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 >
373 {pendingTask.title}386 {pendingTask.title}
374 </Text>387 </Text>
375 <View className="flex-row items-center gap-2">388 <View className="flex-row items-center gap-2">
376 {statusPill}389 {statusPill}
377 {timestamp !== null ? (390 {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>
379 ) : null}399 ) : null}
380 <SymbolView400 <SymbolView
381 name="chevron.right"401 name="chevron.right"
...@@ -395,6 +415,7 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {...@@ -395,6 +415,7 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {
395 accessibilityHint={accessibilityHint}415 accessibilityHint={accessibilityHint}
396 accessibilityLabel={pendingTask.title}416 accessibilityLabel={pendingTask.title}
397 accessibilityRole="button"417 accessibilityRole="button"
418 interactionClassName="bg-thread-hover"
398 onPress={() => onSelectPendingTask(pendingTask)}419 onPress={() => onSelectPendingTask(pendingTask)}
399 style={{420 style={{
400 borderRadius: SIDEBAR_ROW_RADIUS,421 borderRadius: SIDEBAR_ROW_RADIUS,
...@@ -406,13 +427,22 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {...@@ -406,13 +427,22 @@ export const PendingTaskListRow = memo(function PendingTaskListRow(props: {
406 >427 >
407 <View className="gap-[3px]">428 <View className="gap-[3px]">
408 <View className="flex-row items-center justify-between gap-2">429 <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 >
410 {pendingTask.title}434 {pendingTask.title}
411 </Text>435 </Text>
412 <View className="flex-row items-center gap-2">436 <View className="flex-row items-center gap-2">
413 {statusPill}437 {statusPill}
414 {timestamp !== null ? (438 {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 >
416 {timestamp}446 {timestamp}
417 </Text>447 </Text>
418 ) : null}448 ) : null}
...@@ -477,10 +507,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {...@@ -477,10 +507,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
477 const theme = useUniwindTheme();507 const theme = useUniwindTheme();
478 const screenColor = theme["--color-screen"];508 const screenColor = theme["--color-screen"];
479 const drawerColor = theme["--color-drawer"];509 const drawerColor = theme["--color-drawer"];
480 const selectedBackgroundColor = theme["--color-user-bubble"];510 const selectedBackgroundColor = theme["--color-thread-selected"];
481 const materialSelectedBackgroundColor = theme["--color-thread-selected"];
482 const materialSelectedForegroundColor = theme["--color-thread-selected-foreground"];
483 const selectedForegroundColor = theme["--color-user-bubble-foreground"];
484511
485 const {512 const {
486 thread,513 thread,
...@@ -508,24 +535,6 @@ export const ThreadListRow = memo(function ThreadListRow(props: {...@@ -508,24 +535,6 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
508 );535 );
509536
510 const backgroundColor = compact ? screenColor : drawerColor;537 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
530 const handleDelete = useCallback(() => onDeleteThread(thread), [onDeleteThread, thread]);539 const handleDelete = useCallback(() => onDeleteThread(thread), [onDeleteThread, thread]);
531 const handleArchive = useCallback(() => onArchiveThread(thread), [onArchiveThread, thread]);540 const handleArchive = useCallback(() => onArchiveThread(thread), [onArchiveThread, thread]);
...@@ -580,11 +589,9 @@ export const ThreadListRow = memo(function ThreadListRow(props: {...@@ -580,11 +589,9 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
580 [handleArchive, handleDelete, handleRegenerateTitle, handleRename, onNewThreadOnBranch, thread],589 [handleArchive, handleDelete, handleRegenerateTitle, handleRename, onNewThreadOnBranch, thread],
581 );590 );
582591
583 const statusPill = effectiveStatus ? (592 const statusPill = status ? (
584 <View className={`${effectiveStatus.pillClassName} rounded-full px-1.5 py-0.5`}>593 <View className={`${status.pillClassName} rounded-full px-1.5 py-0.5`}>
585 <Text className={`text-3xs font-t3-bold ${effectiveStatus.textClassName}`}>594 <Text className={`text-3xs font-t3-bold ${status.textClassName}`}>{status.label}</Text>
586 {effectiveStatus.label}
587 </Text>
588 </View>595 </View>
589 ) : null;596 ) : null;
590597
...@@ -601,10 +608,8 @@ export const ThreadListRow = memo(function ThreadListRow(props: {...@@ -601,10 +608,8 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
601 compact608 compact
602 ? "accent-icon-subtle"609 ? "accent-icon-subtle"
603 : selected610 : selected
604 ? Platform.OS === "android"611 ? "accent-thread-selected-foreground-muted"
605 ? "accent-thread-selected-foreground-muted"612 : "accent-drawer-foreground-muted"
606 : "accent-user-bubble-foreground-muted"
607 : "accent-foreground-muted"
608 }613 }
609 />614 />
610 ) : null}615 ) : null}
...@@ -613,10 +618,10 @@ export const ThreadListRow = memo(function ThreadListRow(props: {...@@ -613,10 +618,10 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
613 "shrink",618 "shrink",
614 compact ? "text-sm" : "text-xs",619 compact ? "text-sm" : "text-xs",
615 visuallySelected620 visuallySelected
616 ? Platform.OS === "android"621 ? "text-thread-selected-foreground-muted"
617 ? "text-thread-selected-foreground-muted"622 : compact
618 : "text-user-bubble-foreground-muted"623 ? "text-foreground-muted"
619 : "text-foreground-muted",624 : "text-drawer-foreground-muted",
620 )}625 )}
621 numberOfLines={1}626 numberOfLines={1}
622 >627 >
...@@ -633,30 +638,16 @@ export const ThreadListRow = memo(function ThreadListRow(props: {...@@ -633,30 +638,16 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
633 <SymbolView638 <SymbolView
634 name="square.3.layers.3d"639 name="square.3.layers.3d"
635 size={compact ? 13 : 11}640 size={compact ? 13 : 11}
636 tintColor={641 tintColor={pullRequestTintColor(pr, colorScheme)}
637 visuallySelected
638 ? String(effectiveSelectedForeground)
639 : pullRequestTintColor(pr, colorScheme)
640 }
641 />642 />
642 ) : (643 ) : (
643 <PullRequestIcon644 <PullRequestIcon
644 size={compact ? 13 : 11}645 size={compact ? 13 : 11}
645 color={646 color={pullRequestTintColor(pr, colorScheme)}
646 visuallySelected
647 ? String(effectiveSelectedForeground)
648 : pullRequestTintColor(pr, colorScheme)
649 }
650 />647 />
651 )}648 )}
652 <Text649 <Text
653 className={`${compact ? "text-sm" : "text-xs"} font-t3-medium ${650 className={`${compact ? "text-sm" : "text-xs"} font-t3-medium ${pr.textClassName}`}
654 visuallySelected
655 ? Platform.OS === "android"
656 ? "text-thread-selected-foreground"
657 : "text-user-bubble-foreground"
658 : pr.textClassName
659 }`}
660 >651 >
661 {pr.label}652 {pr.label}
662 </Text>653 </Text>
...@@ -669,13 +660,8 @@ export const ThreadListRow = memo(function ThreadListRow(props: {...@@ -669,13 +660,8 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
669 compact ? (660 compact ? (
670 <RowPressable661 <RowPressable
671 key={`${thread.environmentId}:${thread.id}`}662 key={`${thread.environmentId}:${thread.id}`}
672 interactionClassName={663 interactionClassName="bg-row-hover"
673 visuallySelected664 interactionOpacity={visuallySelected ? 0 : 1}
674 ? Platform.OS === "android"
675 ? "bg-thread-selected-foreground"
676 : "bg-user-bubble-foreground"
677 : "bg-primary"
678 }
679 accessibilityHint="Swipe left for archive and delete actions"665 accessibilityHint="Swipe left for archive and delete actions"
680 accessibilityLabel={threadAccessibilityLabel}666 accessibilityLabel={threadAccessibilityLabel}
681 accessibilityRole="button"667 accessibilityRole="button"
...@@ -683,7 +669,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {...@@ -683,7 +669,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
683 style={669 style={
684 Platform.OS === "android"670 Platform.OS === "android"
685 ? {671 ? {
686 backgroundColor: visuallySelected ? effectiveSelectedBackground : backgroundColor,672 backgroundColor: visuallySelected ? selectedBackgroundColor : backgroundColor,
687 borderRadius: 20,673 borderRadius: 20,
688 }674 }
689 : undefined675 : undefined
...@@ -705,10 +691,10 @@ export const ThreadListRow = memo(function ThreadListRow(props: {...@@ -705,10 +691,10 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
705 className={cn(691 className={cn(
706 "flex-1 text-lg font-t3-bold",692 "flex-1 text-lg font-t3-bold",
707 visuallySelected693 visuallySelected
708 ? Platform.OS === "android"694 ? "text-thread-selected-foreground"
709 ? "text-thread-selected-foreground"695 : compact
710 : "text-user-bubble-foreground"696 ? "text-foreground"
711 : "text-foreground",697 : "text-drawer-foreground",
712 )}698 )}
713 numberOfLines={1}699 numberOfLines={1}
714 >700 >
...@@ -723,10 +709,8 @@ export const ThreadListRow = memo(function ThreadListRow(props: {...@@ -723,10 +709,8 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
723 className={cn(709 className={cn(
724 "text-base tabular-nums",710 "text-base tabular-nums",
725 visuallySelected711 visuallySelected
726 ? Platform.OS === "android"712 ? "text-thread-selected-foreground-muted"
727 ? "text-thread-selected-foreground-muted"713 : "text-drawer-foreground-muted",
728 : "text-user-bubble-foreground-muted"
729 : "text-foreground-tertiary",
730 )}714 )}
731 >715 >
732 {timestamp}716 {timestamp}
...@@ -742,6 +726,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {...@@ -742,6 +726,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
742 {props.searchMatch ? (726 {props.searchMatch ? (
743 <ThreadSearchMatchExcerpt727 <ThreadSearchMatchExcerpt
744 compact728 compact
729 sidebar={!compact}
745 match={props.searchMatch}730 match={props.searchMatch}
746 query={props.searchQuery ?? ""}731 query={props.searchQuery ?? ""}
747 selected={visuallySelected}732 selected={visuallySelected}
...@@ -754,13 +739,8 @@ export const ThreadListRow = memo(function ThreadListRow(props: {...@@ -754,13 +739,8 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
754 ) : (739 ) : (
755 <RowPressable740 <RowPressable
756 key={`${thread.environmentId}:${thread.id}`}741 key={`${thread.environmentId}:${thread.id}`}
757 interactionClassName={742 interactionClassName="bg-thread-hover"
758 visuallySelected743 interactionOpacity={visuallySelected ? 0 : 1}
759 ? Platform.OS === "android"
760 ? "bg-thread-selected-foreground"
761 : "bg-user-bubble-foreground"
762 : "bg-primary"
763 }
764 accessibilityHint="Opens the thread"744 accessibilityHint="Opens the thread"
765 accessibilityLabel={threadAccessibilityLabel}745 accessibilityLabel={threadAccessibilityLabel}
766 accessibilityRole="button"746 accessibilityRole="button"
...@@ -770,7 +750,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {...@@ -770,7 +750,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
770 onSelectThread(thread);750 onSelectThread(thread);
771 }}751 }}
772 style={{752 style={{
773 backgroundColor: visuallySelected ? effectiveSelectedBackground : backgroundColor,753 backgroundColor: visuallySelected ? selectedBackgroundColor : backgroundColor,
774 borderRadius: Platform.OS === "android" ? 20 : SIDEBAR_ROW_RADIUS,754 borderRadius: Platform.OS === "android" ? 20 : SIDEBAR_ROW_RADIUS,
775 minHeight: 64,755 minHeight: 64,
776 justifyContent: "center",756 justifyContent: "center",
...@@ -784,10 +764,10 @@ export const ThreadListRow = memo(function ThreadListRow(props: {...@@ -784,10 +764,10 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
784 className={cn(764 className={cn(
785 "flex-1 text-base font-t3-medium",765 "flex-1 text-base font-t3-medium",
786 visuallySelected766 visuallySelected
787 ? Platform.OS === "android"767 ? "text-thread-selected-foreground"
788 ? "text-thread-selected-foreground"768 : compact
789 : "text-user-bubble-foreground"769 ? "text-foreground"
790 : "text-foreground",770 : "text-drawer-foreground",
791 )}771 )}
792 numberOfLines={1}772 numberOfLines={1}
793 >773 >
...@@ -802,10 +782,10 @@ export const ThreadListRow = memo(function ThreadListRow(props: {...@@ -802,10 +782,10 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
802 className={cn(782 className={cn(
803 "text-xs tabular-nums",783 "text-xs tabular-nums",
804 visuallySelected784 visuallySelected
805 ? Platform.OS === "android"785 ? "text-thread-selected-foreground-muted"
806 ? "text-thread-selected-foreground-muted"786 : compact
807 : "text-user-bubble-foreground-muted"787 ? "text-foreground-muted"
808 : "text-foreground-muted",788 : "text-drawer-foreground-muted",
809 )}789 )}
810 numberOfLines={1}790 numberOfLines={1}
811 >791 >
...@@ -815,6 +795,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {...@@ -815,6 +795,7 @@ export const ThreadListRow = memo(function ThreadListRow(props: {
815 </View>795 </View>
816 {props.searchMatch ? (796 {props.searchMatch ? (
817 <ThreadSearchMatchExcerpt797 <ThreadSearchMatchExcerpt
798 sidebar={!compact}
818 match={props.searchMatch}799 match={props.searchMatch}
819 query={props.searchQuery ?? ""}800 query={props.searchQuery ?? ""}
820 selected={visuallySelected}801 selected={visuallySelected}
apps/mobile/src/features/threads/thread-list-v2-items.tsx+109-40
...@@ -64,7 +64,7 @@ const STATUS_LABEL_BY_STATUS: Partial<...@@ -64,7 +64,7 @@ const STATUS_LABEL_BY_STATUS: Partial<
64 Record<ThreadListV2Status, { label: string; className: string }>64 Record<ThreadListV2Status, { label: string; className: string }>
65> = {65> = {
66 approval: { label: "Approval", className: "text-warning-foreground" },66 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" },
68 working: { label: "Working", className: "text-adaptive-sky-600-400" },68 working: { label: "Working", className: "text-adaptive-sky-600-400" },
69 failed: { label: "Failed", className: "text-danger-foreground" },69 failed: { label: "Failed", className: "text-danger-foreground" },
70};70};
...@@ -112,6 +112,7 @@ function ThreadListV2Section(props: {...@@ -112,6 +112,7 @@ function ThreadListV2Section(props: {
112 };112 };
113}) {113}) {
114 const snoozed = props.tone === "snoozed";114 const snoozed = props.tone === "snoozed";
115 const sidebarPane = props.pane === "sidebar";
115 const className = cn(116 const className = cn(
116 "mb-1.5 mt-4 flex-row items-center gap-2.5",117 "mb-1.5 mt-4 flex-row items-center gap-2.5",
117 props.pane === "sidebar" ? "px-3" : "px-5",118 props.pane === "sidebar" ? "px-3" : "px-5",
...@@ -121,17 +122,32 @@ function ThreadListV2Section(props: {...@@ -121,17 +122,32 @@ function ThreadListV2Section(props: {
121 <Text122 <Text
122 className={cn(123 className={cn(
123 "text-xs font-t3-medium",124 "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",
125 )}130 )}
126 >131 >
127 {props.label}132 {props.label}
128 </Text>133 </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 />
130 {props.disclosure ? (140 {props.disclosure ? (
131 <SymbolView141 <SymbolView
132 name="chevron.down"142 name="chevron.down"
133 size={10}143 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 }
135 type="monochrome"151 type="monochrome"
136 style={{ transform: [{ rotate: props.disclosure.expanded ? "180deg" : "0deg" }] }}152 style={{ transform: [{ rotate: props.disclosure.expanded ? "180deg" : "0deg" }] }}
137 />153 />
...@@ -209,6 +225,7 @@ export const ThreadListV2SettledShelfHeader = memo(function ThreadListV2SettledS...@@ -209,6 +225,7 @@ export const ThreadListV2SettledShelfHeader = memo(function ThreadListV2SettledS
209});225});
210226
211export const ThreadListV2ShowMoreRow = memo(function ThreadListV2ShowMoreRow(props: {227export const ThreadListV2ShowMoreRow = memo(function ThreadListV2ShowMoreRow(props: {
228 readonly pane?: "screen" | "sidebar";
212 readonly hiddenCount: number;229 readonly hiddenCount: number;
213 readonly onPress: () => void;230 readonly onPress: () => void;
214}) {231}) {
...@@ -220,7 +237,13 @@ export const ThreadListV2ShowMoreRow = memo(function ThreadListV2ShowMoreRow(pro...@@ -220,7 +237,13 @@ export const ThreadListV2ShowMoreRow = memo(function ThreadListV2ShowMoreRow(pro
220 className="mx-4 mt-2 items-center rounded-lg border border-dashed border-border py-2.5"237 className="mx-4 mt-2 items-center rounded-lg border border-dashed border-border py-2.5"
221 style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1 })}238 style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1 })}
222 >239 >
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 >
224 Show more ({props.hiddenCount} settled hidden)247 Show more ({props.hiddenCount} settled hidden)
225 </Text>248 </Text>
226 </Pressable>249 </Pressable>
...@@ -282,7 +305,13 @@ export const ThreadListV2PendingRow = memo(function ThreadListV2PendingRow(props...@@ -282,7 +305,13 @@ export const ThreadListV2PendingRow = memo(function ThreadListV2PendingRow(props
282 workspaceRoot={props.project.workspaceRoot}305 workspaceRoot={props.project.workspaceRoot}
283 />306 />
284 ) : null}307 ) : 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 >
286 {projectTitle}315 {projectTitle}
287 </Text>316 </Text>
288 {isDraft ? (317 {isDraft ? (
...@@ -296,33 +325,67 @@ export const ThreadListV2PendingRow = memo(function ThreadListV2PendingRow(props...@@ -296,33 +325,67 @@ export const ThreadListV2PendingRow = memo(function ThreadListV2PendingRow(props
296 <Text className="text-xs text-adaptive-amber-700-300">Draft</Text>325 <Text className="text-xs text-adaptive-amber-700-300">Draft</Text>
297 </View>326 </View>
298 ) : (327 ) : (
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>
300 )}336 )}
301 </View>337 </View>
302 {/* One line, unlike the two an active row allows: a queued title is338 {/* One line, unlike the two an active row allows: a queued title is
303 derived from the whole prompt rather than written as a title, so the339 derived from the whole prompt rather than written as a title, so the
304 second line is usually a stray word or emoji rather than meaning. */}340 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 >
306 {pendingTask.title}348 {pendingTask.title}
307 </Text>349 </Text>
308 {branch || props.environmentLabel ? (350 {branch || props.environmentLabel ? (
309 <View className="mt-1 flex-row items-center gap-1">351 <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 >
311 {branch ? (359 {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 >
313 {branch}367 {branch}
314 </Text>368 </Text>
315 ) : null}369 ) : null}
316 {branch && props.environmentLabel ? " · " : null}370 {branch && props.environmentLabel ? " · " : null}
317 {props.environmentLabel ? (371 {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>
319 ) : null}380 ) : null}
320 </Text>381 </Text>
321 {props.environmentLabel && props.environmentMachine ? (382 {props.environmentLabel && props.environmentMachine ? (
322 <EnvironmentMachineSymbol383 <EnvironmentMachineSymbol
323 kind={props.environmentMachine}384 kind={props.environmentMachine}
324 size={11}385 size={11}
325 tintColorClassName="accent-foreground-tertiary"386 tintColorClassName={
387 sidebarPane ? "accent-drawer-foreground-muted" : "accent-foreground-tertiary"
388 }
326 />389 />
327 ) : null}390 ) : null}
328 </View>391 </View>
...@@ -350,6 +413,7 @@ export const ThreadListV2PendingRow = memo(function ThreadListV2PendingRow(props...@@ -350,6 +413,7 @@ export const ThreadListV2PendingRow = memo(function ThreadListV2PendingRow(props
350 accessibilityRole="button"413 accessibilityRole="button"
351 key={pendingTask.key}414 key={pendingTask.key}
352 className={sidebarPane ? "bg-drawer" : "bg-screen"}415 className={sidebarPane ? "bg-drawer" : "bg-screen"}
416 interactionClassName={sidebarPane ? "bg-thread-hover" : "bg-row-hover"}
353 onPress={() => onSelectPendingTask(pendingTask)}417 onPress={() => onSelectPendingTask(pendingTask)}
354 style={418 style={
355 sidebarPane419 sidebarPane
...@@ -759,8 +823,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {...@@ -759,8 +823,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
759 ? `Opens the thread. Swipe left to ${primaryAction.label.toLowerCase()}.`823 ? `Opens the thread. Swipe left to ${primaryAction.label.toLowerCase()}.`
760 : `Opens the thread. Swipe left for ${primaryAction.label.toLowerCase()} and snooze actions.`;824 : `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, so826 // Sidebar rows use navigation foregrounds on their active and idle surfaces.
763 // every piece of row text must use that surface's paired foreground.
764 const cardContent = (827 const cardContent = (
765 <>828 <>
766 <View className="flex-row items-center gap-1.5">829 <View className="flex-row items-center gap-1.5">
...@@ -776,7 +839,9 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {...@@ -776,7 +839,9 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
776 <Text839 <Text
777 className={cn(840 className={cn(
778 "flex-1 text-sm font-t3-medium",841 "flex-1 text-sm font-t3-medium",
779 selected ? selectedThreadRowColors.mutedForegroundClassName : "text-foreground-muted",842 selected
843 ? selectedThreadRowColors.mutedForegroundClassName
844 : rowAppearance.mutedForegroundClassName,
780 )}845 )}
781 numberOfLines={1}846 numberOfLines={1}
782 >847 >
...@@ -787,16 +852,17 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {...@@ -787,16 +852,17 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
787 <SymbolView852 <SymbolView
788 name="pin"853 name="pin"
789 size={11}854 size={11}
790 tintColorClassName="accent-foreground-muted"855 tintColorClassName={rowAppearance.mutedIconTintClassName}
791 type="monochrome"856 type="monochrome"
792 />857 />
793 ) : null}858 ) : null}
794 <Text859 <Text
795 className={cn(860 className={cn(
796 "text-xs tabular-nums",861 "text-xs tabular-nums",
797 selected862 statusLabel?.className ??
798 ? selectedThreadRowColors.foregroundClassName863 (selected
799 : (statusLabel?.className ?? "text-foreground-tertiary"),864 ? selectedThreadRowColors.foregroundClassName
865 : rowAppearance.tertiaryForegroundClassName),
800 )}866 )}
801 >867 >
802 {statusLabel?.label ?? timeLabel}868 {statusLabel?.label ?? timeLabel}
...@@ -805,7 +871,9 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {...@@ -805,7 +871,9 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
805 <Text871 <Text
806 className={cn(872 className={cn(
807 "mt-1 text-base font-t3-medium",873 "mt-1 text-base font-t3-medium",
808 selected ? selectedThreadRowColors.foregroundClassName : "text-foreground",874 selected
875 ? selectedThreadRowColors.foregroundClassName
876 : rowAppearance.foregroundClassName,
809 )}877 )}
810 numberOfLines={2}878 numberOfLines={2}
811 >879 >
...@@ -814,6 +882,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {...@@ -814,6 +882,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
814 {props.searchMatch ? (882 {props.searchMatch ? (
815 <View className="mt-1">883 <View className="mt-1">
816 <ThreadSearchMatchExcerpt884 <ThreadSearchMatchExcerpt
885 sidebar={sidebarPane}
817 match={props.searchMatch}886 match={props.searchMatch}
818 query={props.searchQuery ?? ""}887 query={props.searchQuery ?? ""}
819 selected={selected}888 selected={selected}
...@@ -846,7 +915,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {...@@ -846,7 +915,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
846 "shrink text-xs",915 "shrink text-xs",
847 selected916 selected
848 ? selectedThreadRowColors.mutedForegroundClassName917 ? selectedThreadRowColors.mutedForegroundClassName
849 : "text-foreground-muted",918 : rowAppearance.mutedForegroundClassName,
850 )}919 )}
851 numberOfLines={1}920 numberOfLines={1}
852 >921 >
...@@ -856,7 +925,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {...@@ -856,7 +925,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
856 "text-xs",925 "text-xs",
857 selected926 selected
858 ? selectedThreadRowColors.mutedForegroundClassName927 ? selectedThreadRowColors.mutedForegroundClassName
859 : "text-foreground-muted",928 : rowAppearance.mutedForegroundClassName,
860 )}929 )}
861 style={{ fontFamily: MONO_FONT }}930 style={{ fontFamily: MONO_FONT }}
862 >931 >
...@@ -870,7 +939,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {...@@ -870,7 +939,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
870 "text-xs",939 "text-xs",
871 selected940 selected
872 ? selectedThreadRowColors.mutedForegroundClassName941 ? selectedThreadRowColors.mutedForegroundClassName
873 : "text-foreground-tertiary",942 : rowAppearance.tertiaryForegroundClassName,
874 )}943 )}
875 >944 >
876 {props.environmentLabel}945 {props.environmentLabel}
...@@ -884,7 +953,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {...@@ -884,7 +953,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
884 tintColorClassName={953 tintColorClassName={
885 selected954 selected
886 ? selectedThreadRowColors.mutedIconTintClassName955 ? selectedThreadRowColors.mutedIconTintClassName
887 : "accent-foreground-tertiary"956 : rowAppearance.tertiaryIconTintClassName
888 }957 }
889 />958 />
890 ) : null}959 ) : null}
...@@ -899,24 +968,19 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {...@@ -899,24 +968,19 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
899 name={pr.kind === "stack" ? "square.3.layers.3d" : "arrow.triangle.pull"}968 name={pr.kind === "stack" ? "square.3.layers.3d" : "arrow.triangle.pull"}
900 size={12}969 size={12}
901 tintColorClassName={970 tintColorClassName={
902 selected971 pr.state === null || pr.isDraft
903 ? selectedThreadRowColors.iconTintClassName972 ? rowAppearance.mutedIconTintClassName
904 : pr.state === null || pr.isDraft973 : pr.state === "open"
905 ? "accent-foreground-muted"974 ? "accent-adaptive-emerald-600-400"
906 : pr.state === "open"975 : pr.state === "closed"
907 ? "accent-adaptive-emerald-600-400"976 ? "accent-adaptive-rose-600-400"
908 : pr.state === "closed"977 : "accent-adaptive-violet-600-400"
909 ? "accent-adaptive-rose-600-400"
910 : "accent-adaptive-violet-600-400"
911 }978 }
912 />979 />
913 ) : null}980 ) : null}
914 <Text981 <Text
915 accessibilityLabel={pr.accessibilityLabel}982 accessibilityLabel={pr.accessibilityLabel}
916 className={cn(983 className={cn("text-xs", pr.textClassName)}
917 "text-xs",
918 selected ? selectedThreadRowColors.foregroundClassName : pr.textClassName,
919 )}
920 style={{ fontFamily: MONO_FONT }}984 style={{ fontFamily: MONO_FONT }}
921 >985 >
922 {pr.kind === "stack" || pr.others > 0 ? pr.label : `#${pr.label}`}986 {pr.kind === "stack" || pr.others > 0 ? pr.label : `#${pr.label}`}
...@@ -942,6 +1006,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {...@@ -942,6 +1006,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
942 <RowPressable1006 <RowPressable
943 key={`${thread.environmentId}:${thread.id}`}1007 key={`${thread.environmentId}:${thread.id}`}
944 interactionClassName={rowAppearance.interactionClassName}1008 interactionClassName={rowAppearance.interactionClassName}
1009 interactionOpacity={rowAppearance.interactionOpacity}
945 className={rowAppearance.className}1010 className={rowAppearance.className}
946 accessibilityHint={swipeAccessibilityHint}1011 accessibilityHint={swipeAccessibilityHint}
947 accessibilityLabel={1012 accessibilityLabel={
...@@ -974,6 +1039,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {...@@ -974,6 +1039,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
974 <RowPressable1039 <RowPressable
975 key={`${thread.environmentId}:${thread.id}`}1040 key={`${thread.environmentId}:${thread.id}`}
976 interactionClassName={rowAppearance.interactionClassName}1041 interactionClassName={rowAppearance.interactionClassName}
1042 interactionOpacity={rowAppearance.interactionOpacity}
977 accessibilityHint={swipeAccessibilityHint}1043 accessibilityHint={swipeAccessibilityHint}
978 accessibilityLabel={1044 accessibilityLabel={
979 props.hasQueuedMessages ? `${thread.title}, messages queued to send` : thread.title1045 props.hasQueuedMessages ? `${thread.title}, messages queued to send` : thread.title
...@@ -1009,7 +1075,9 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {...@@ -1009,7 +1075,9 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
1009 <Text1075 <Text
1010 className={cn(1076 className={cn(
1011 "text-base",1077 "text-base",
1012 selected ? selectedThreadRowColors.foregroundClassName : "text-foreground-muted",1078 selected
1079 ? selectedThreadRowColors.foregroundClassName
1080 : rowAppearance.mutedForegroundClassName,
1013 )}1081 )}
1014 numberOfLines={1}1082 numberOfLines={1}
1015 >1083 >
...@@ -1017,6 +1085,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {...@@ -1017,6 +1085,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
1017 </Text>1085 </Text>
1018 {props.searchMatch ? (1086 {props.searchMatch ? (
1019 <ThreadSearchMatchExcerpt1087 <ThreadSearchMatchExcerpt
1088 sidebar={sidebarPane}
1020 match={props.searchMatch}1089 match={props.searchMatch}
1021 query={props.searchQuery ?? ""}1090 query={props.searchQuery ?? ""}
1022 selected={selected}1091 selected={selected}
...@@ -1030,8 +1099,8 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {...@@ -1030,8 +1099,8 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
1030 selected1099 selected
1031 ? selectedThreadRowColors.mutedForegroundClassName1100 ? selectedThreadRowColors.mutedForegroundClassName
1032 : snoozedRow1101 : snoozedRow
1033 ? "text-foreground-secondary"1102 ? rowAppearance.mutedForegroundClassName
1034 : "text-foreground-tertiary",1103 : rowAppearance.tertiaryForegroundClassName,
1035 )}1104 )}
1036 style={{ fontFamily: MONO_FONT }}1105 style={{ fontFamily: MONO_FONT }}
1037 >1106 >
apps/mobile/src/features/threads/thread-list-v2-row-appearance.android.ts+19-5
...@@ -22,8 +22,9 @@ export function getThreadListV2RowAppearance(...@@ -22,8 +22,9 @@ export function getThreadListV2RowAppearance(
22 selected: boolean,22 selected: boolean,
23) {23) {
24 const selectedBackgroundColor = theme["--color-thread-selected"];24 const selectedBackgroundColor = theme["--color-thread-selected"];
25 const backgroundColor = theme[sidebarPane ? "--color-drawer" : "--color-screen"];
25 const style: ViewStyle = {26 const style: ViewStyle = {
26 backgroundColor: selected ? selectedBackgroundColor : theme["--color-screen"],27 backgroundColor: selected ? selectedBackgroundColor : backgroundColor,
27 borderRadius: 20,28 borderRadius: 20,
28 };29 };
29 const swipeContainerStyle: ViewStyle = {30 const swipeContainerStyle: ViewStyle = {
...@@ -35,12 +36,25 @@ export function getThreadListV2RowAppearance(...@@ -35,12 +36,25 @@ export function getThreadListV2RowAppearance(
3536
36 return {37 return {
37 className: undefined,38 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",
39 style,54 style,
40 cardStyle: sidebarPane ? { ...style, paddingHorizontal: 12, paddingVertical: 10 } : style,55 cardStyle: sidebarPane ? { ...style, paddingHorizontal: 12, paddingVertical: 10 } : style,
41 swipeContainerStyle,56 swipeContainerStyle,
42 swipeBackgroundColor: theme["--color-screen"],57 swipeBackgroundColor: backgroundColor,
43 providerIconSurfaceColor:58 providerIconSurfaceColor: selected ? selectedBackgroundColor : backgroundColor,
44 sidebarPane && selected ? selectedBackgroundColor : theme["--color-screen"],
45 };59 };
46}60}
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";...@@ -6,10 +6,10 @@ export const THREAD_LIST_V2_ROW_CONTENT_CLASS_NAME = "px-5 py-2.5";
6export const THREAD_LIST_V2_ROW_DIVIDERS = true;6export const THREAD_LIST_V2_ROW_DIVIDERS = true;
77
8export const selectedThreadRowColors = {8export const selectedThreadRowColors = {
9 foregroundClassName: "text-user-bubble-foreground",9 foregroundClassName: "text-thread-selected-foreground",
10 mutedForegroundClassName: "text-user-bubble-foreground-muted",10 mutedForegroundClassName: "text-thread-selected-foreground-muted",
11 iconTintClassName: "accent-user-bubble-foreground",11 iconTintClassName: "accent-thread-selected-foreground",
12 mutedIconTintClassName: "accent-user-bubble-foreground-muted",12 mutedIconTintClassName: "accent-thread-selected-foreground-muted",
13};13};
1414
15export function getThreadListV2NewBranchMenuTitle(_branch: string) {15export function getThreadListV2NewBranchMenuTitle(_branch: string) {
...@@ -21,7 +21,7 @@ export function getThreadListV2RowAppearance(...@@ -21,7 +21,7 @@ export function getThreadListV2RowAppearance(
21 sidebarPane: boolean,21 sidebarPane: boolean,
22 selected: boolean,22 selected: boolean,
23) {23) {
24 const selectedBackgroundColor = theme["--color-user-bubble"];24 const selectedBackgroundColor = theme["--color-thread-selected"];
25 const style: ViewStyle | undefined = sidebarPane25 const style: ViewStyle | undefined = sidebarPane
26 ? {26 ? {
27 backgroundColor: selected ? selectedBackgroundColor : theme["--color-drawer"],27 backgroundColor: selected ? selectedBackgroundColor : theme["--color-drawer"],
...@@ -34,7 +34,21 @@ export function getThreadListV2RowAppearance(...@@ -34,7 +34,21 @@ export function getThreadListV2RowAppearance(
3434
35 return {35 return {
36 className: sidebarPane ? undefined : "bg-screen",36 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",
38 style,52 style,
39 cardStyle: sidebarPane ? { ...style, paddingHorizontal: 12, paddingVertical: 10 } : undefined,53 cardStyle: sidebarPane ? { ...style, paddingHorizontal: 12, paddingVertical: 10 } : undefined,
40 swipeContainerStyle,54 swipeContainerStyle,
apps/mobile/src/features/threads/thread-search-match.tsx+16-14
...@@ -1,4 +1,3 @@...@@ -1,4 +1,3 @@
1import { Platform } from "react-native";
2import type { EnvironmentThreadSearchMatch } from "@t3tools/client-runtime/state/thread-search";1import type { EnvironmentThreadSearchMatch } from "@t3tools/client-runtime/state/thread-search";
32
4import { AppText as Text } from "../../components/AppText";3import { AppText as Text } from "../../components/AppText";
...@@ -49,6 +48,7 @@ export function ThreadSearchMatchExcerpt(props: {...@@ -49,6 +48,7 @@ export function ThreadSearchMatchExcerpt(props: {
49 readonly query: string;48 readonly query: string;
50 readonly selected?: boolean;49 readonly selected?: boolean;
51 readonly compact?: boolean;50 readonly compact?: boolean;
51 readonly sidebar?: boolean;
52}) {52}) {
53 const isUser = props.match.source === "user";53 const isUser = props.match.source === "user";
54 const parts = splitHighlightParts(props.match.snippet, props.query);54 const parts = splitHighlightParts(props.match.snippet, props.query);
...@@ -57,10 +57,10 @@ export function ThreadSearchMatchExcerpt(props: {...@@ -57,10 +57,10 @@ export function ThreadSearchMatchExcerpt(props: {
57 className={cn(57 className={cn(
58 props.compact ? "text-sm" : "text-xs",58 props.compact ? "text-sm" : "text-xs",
59 props.selected59 props.selected
60 ? Platform.OS === "android"60 ? "text-thread-selected-foreground-muted"
61 ? "text-thread-selected-foreground-muted"61 : props.sidebar
62 : "text-user-bubble-foreground-muted"62 ? "text-drawer-foreground-muted"
63 : "text-foreground-muted",63 : "text-foreground-muted",
64 )}64 )}
65 numberOfLines={1}65 numberOfLines={1}
66 >66 >
...@@ -68,11 +68,11 @@ export function ThreadSearchMatchExcerpt(props: {...@@ -68,11 +68,11 @@ export function ThreadSearchMatchExcerpt(props: {
68 className={cn(68 className={cn(
69 props.compact ? "text-sm font-t3-medium" : "text-xs font-t3-medium",69 props.compact ? "text-sm font-t3-medium" : "text-xs font-t3-medium",
70 props.selected70 props.selected
71 ? Platform.OS === "android"71 ? "text-thread-selected-foreground"
72 ? "text-thread-selected-foreground"
73 : "text-user-bubble-foreground"
74 : isUser72 : isUser
75 ? "text-foreground-secondary"73 ? props.sidebar
74 ? "text-drawer-foreground-muted"
75 : "text-foreground-secondary"
76 : "text-adaptive-emerald-600-400",76 : "text-adaptive-emerald-600-400",
77 )}77 )}
78 >78 >
...@@ -84,12 +84,14 @@ export function ThreadSearchMatchExcerpt(props: {...@@ -84,12 +84,14 @@ export function ThreadSearchMatchExcerpt(props: {
84 props.compact ? "text-sm" : "text-xs",84 props.compact ? "text-sm" : "text-xs",
85 part.highlighted && "font-t3-bold",85 part.highlighted && "font-t3-bold",
86 props.selected86 props.selected
87 ? Platform.OS === "android"87 ? "text-thread-selected-foreground"
88 ? "text-thread-selected-foreground"
89 : "text-user-bubble-foreground"
90 : part.highlighted88 : part.highlighted
91 ? "text-foreground"89 ? props.sidebar
92 : "text-foreground-muted",90 ? "text-drawer-foreground"
91 : "text-foreground"
92 : props.sidebar
93 ? "text-drawer-foreground-muted"
94 : "text-foreground-muted",
93 )}95 )}
94 key={part.start}96 key={part.start}
95 >97 >
apps/mobile/src/features/threads/thread-work-log.tsx+3-3
...@@ -882,7 +882,7 @@ const ThreadWorkLogRow = memo(function ThreadWorkLogRow(...@@ -882,7 +882,7 @@ const ThreadWorkLogRow = memo(function ThreadWorkLogRow(
882 entering={WORK_LOG_DETAIL_ENTER_TRANSITION}882 entering={WORK_LOG_DETAIL_ENTER_TRANSITION}
883 exiting={WORK_LOG_DETAIL_EXIT_TRANSITION}883 exiting={WORK_LOG_DETAIL_EXIT_TRANSITION}
884 layout={WORK_LOG_LAYOUT_TRANSITION}884 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"
886 >886 >
887 {row.workEntry.questionAnswer ? (887 {row.workEntry.questionAnswer ? (
888 <QuestionAnswerHistory888 <QuestionAnswerHistory
...@@ -1037,7 +1037,7 @@ export const ThreadAgentSpawnCard = memo(function ThreadAgentSpawnCard(props: {...@@ -1037,7 +1037,7 @@ export const ThreadAgentSpawnCard = memo(function ThreadAgentSpawnCard(props: {
1037 props.onToggle();1037 props.onToggle();
1038 }}1038 }}
1039 onLongPress={props.onCopy}1039 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"
1041 >1041 >
1042 <View className="flex-row items-center gap-2">1042 <View className="flex-row items-center gap-2">
1043 <View className="h-6 w-6 shrink-0 items-center justify-center">1043 <View className="h-6 w-6 shrink-0 items-center justify-center">
...@@ -1094,7 +1094,7 @@ export const ThreadAgentSpawnCard = memo(function ThreadAgentSpawnCard(props: {...@@ -1094,7 +1094,7 @@ export const ThreadAgentSpawnCard = memo(function ThreadAgentSpawnCard(props: {
1094 entering={WORK_LOG_DETAIL_ENTER_TRANSITION}1094 entering={WORK_LOG_DETAIL_ENTER_TRANSITION}
1095 exiting={WORK_LOG_DETAIL_EXIT_TRANSITION}1095 exiting={WORK_LOG_DETAIL_EXIT_TRANSITION}
1096 layout={WORK_LOG_LAYOUT_TRANSITION}1096 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"
1098 >1098 >
1099 {summary.members.map((member) => (1099 {summary.members.map((member) => (
1100 <View key={member.title} className="gap-px">1100 <View key={member.title} className="gap-px">
apps/mobile/src/features/threads/threadPresentation.ts+7-23
...@@ -12,10 +12,6 @@ export type ThreadStatusKind =...@@ -12,10 +12,6 @@ export type ThreadStatusKind =
1212
13export interface ThreadStatusPresentation extends StatusTone {13export interface ThreadStatusPresentation extends StatusTone {
14 readonly kind: ThreadStatusKind;14 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;
19 /** Whether the indicator represents in-flight activity. */15 /** Whether the indicator represents in-flight activity. */
20 readonly pulse: boolean;16 readonly pulse: boolean;
21}17}
...@@ -44,8 +40,6 @@ export function resolveThreadStatus(...@@ -44,8 +40,6 @@ export function resolveThreadStatus(
44 label: "Needs Approval",40 label: "Needs Approval",
45 pillClassName: "bg-warning",41 pillClassName: "bg-warning",
46 textClassName: "text-warning-foreground",42 textClassName: "text-warning-foreground",
47 iconColor: "#ff9f0a",
48 iconBackground: "rgba(255,159,10,0.22)",
49 pulse: false,43 pulse: false,
50 };44 };
51 }45 }
...@@ -54,10 +48,8 @@ export function resolveThreadStatus(...@@ -54,10 +48,8 @@ export function resolveThreadStatus(
54 return {48 return {
55 kind: "awaiting-input",49 kind: "awaiting-input",
56 label: "Awaiting Input",50 label: "Awaiting Input",
57 pillClassName: "bg-primary/10",51 pillClassName: "bg-adaptive-indigo-500-a12-a16",
58 textClassName: "text-foreground-secondary",52 textClassName: "text-adaptive-indigo-600-300",
59 iconColor: "#5e5ce6",
60 iconBackground: "rgba(94,92,230,0.22)",
61 pulse: false,53 pulse: false,
62 };54 };
63 }55 }
...@@ -66,10 +58,8 @@ export function resolveThreadStatus(...@@ -66,10 +58,8 @@ export function resolveThreadStatus(
66 return {58 return {
67 kind: "working",59 kind: "working",
68 label: "Working",60 label: "Working",
69 pillClassName: "bg-primary/10",61 pillClassName: "bg-adaptive-sky-500-a12-a16",
70 textClassName: "text-adaptive-sky-600-400",62 textClassName: "text-adaptive-sky-600-400",
71 iconColor: "#0a84ff",
72 iconBackground: "rgba(10,132,255,0.22)",
73 pulse: true,63 pulse: true,
74 };64 };
75 }65 }
...@@ -78,10 +68,8 @@ export function resolveThreadStatus(...@@ -78,10 +68,8 @@ export function resolveThreadStatus(
78 return {68 return {
79 kind: "connecting",69 kind: "connecting",
80 label: "Connecting",70 label: "Connecting",
81 pillClassName: "bg-primary/10",71 pillClassName: "bg-adaptive-sky-500-a12-a16",
82 textClassName: "text-foreground-secondary",72 textClassName: "text-adaptive-sky-600-400",
83 iconColor: "#0a84ff",
84 iconBackground: "rgba(10,132,255,0.22)",
85 pulse: true,73 pulse: true,
86 };74 };
87 }75 }
...@@ -92,8 +80,6 @@ export function resolveThreadStatus(...@@ -92,8 +80,6 @@ export function resolveThreadStatus(
92 label: "Error",80 label: "Error",
93 pillClassName: "bg-danger",81 pillClassName: "bg-danger",
94 textClassName: "text-danger-foreground",82 textClassName: "text-danger-foreground",
95 iconColor: "#ff453a",
96 iconBackground: "rgba(255,69,58,0.22)",
97 pulse: false,83 pulse: false,
98 };84 };
99 }85 }
...@@ -106,10 +92,8 @@ export function resolveThreadStatus(...@@ -106,10 +92,8 @@ export function resolveThreadStatus(
106 return {92 return {
107 kind: "plan-ready",93 kind: "plan-ready",
108 label: "Plan Ready",94 label: "Plan Ready",
109 pillClassName: "bg-primary/10",95 pillClassName: "bg-adaptive-violet-500-a12-a16",
110 textClassName: "text-foreground-secondary",96 textClassName: "text-adaptive-violet-600-400",
111 iconColor: "#bf5af2",
112 iconBackground: "rgba(191,90,242,0.22)",
113 pulse: false,97 pulse: false,
114 };98 };
115 }99 }
apps/mobile/src/features/threads/worktree-setup-card.tsx+26-28
...@@ -72,7 +72,7 @@ export function WorktreeSetupCard(props: WorktreeSetupCardProps) {...@@ -72,7 +72,7 @@ export function WorktreeSetupCard(props: WorktreeSetupCardProps) {
72 />72 />
73 {!handedOff ? (73 {!handedOff ? (
74 <Text74 <Text
75 className="text-2xs text-adaptive-neutral-600-400"75 className="text-2xs text-foreground-secondary"
76 style={{ fontVariant: ["tabular-nums"] }}76 style={{ fontVariant: ["tabular-nums"] }}
77 >77 >
78 {elapsed(snapshot.startedAt, snapshot.endedAt, now)}78 {elapsed(snapshot.startedAt, snapshot.endedAt, now)}
...@@ -99,25 +99,21 @@ export function WorktreeSetupCard(props: WorktreeSetupCardProps) {...@@ -99,25 +99,21 @@ export function WorktreeSetupCard(props: WorktreeSetupCardProps) {
99 {backgroundSetup ? (99 {backgroundSetup ? (
100 <ActivityIndicator100 <ActivityIndicator
101 size="small"101 size="small"
102 colorClassName="accent-adaptive-neutral-500-400"102 colorClassName="accent-icon-muted"
103 style={{ width: 12, height: 12, transform: [{ scale: 0.65 }] }}103 style={{ width: 12, height: 12, transform: [{ scale: 0.65 }] }}
104 />104 />
105 ) : failed ? (105 ) : failed ? (
106 <SymbolView106 <SymbolView
107 name="exclamationmark.circle"107 name="exclamationmark.circle"
108 size={12}108 size={12}
109 tintColorClassName="accent-adaptive-rose-700-300"109 tintColorClassName="accent-danger-foreground"
110 />110 />
111 ) : null}111 ) : 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">
113 {backgroundSetup ? scriptName : "Details"}113 {backgroundSetup ? scriptName : "Details"}
114 </Text>114 </Text>
115 {!backgroundSetup ? (115 {!backgroundSetup ? (
116 <SymbolView116 <SymbolView name="chevron.right" size={10} tintColorClassName="accent-icon-muted" />
117 name="chevron.right"
118 size={10}
119 tintColorClassName="accent-adaptive-neutral-500-400"
120 />
121 ) : null}117 ) : null}
122 </View>118 </View>
123 </Pressable>119 </Pressable>
...@@ -166,14 +162,19 @@ function HeaderLabel({...@@ -166,14 +162,19 @@ function HeaderLabel({
166 failed?: boolean;162 failed?: boolean;
167}) {163}) {
168 return active ? (164 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 />
170 ) : (171 ) : (
171 <Text172 <Text
172 numberOfLines={1}173 numberOfLines={1}
173 className={174 className={
174 failed175 failed
175 ? "flex-1 text-sm text-adaptive-rose-700-300"176 ? "flex-1 text-sm text-danger-foreground"
176 : "flex-1 text-sm text-adaptive-neutral-600-400"177 : "flex-1 text-sm text-foreground-secondary"
177 }178 }
178 style={{ fontVariant: ["tabular-nums"] }}179 style={{ fontVariant: ["tabular-nums"] }}
179 >180 >
...@@ -232,7 +233,7 @@ function SetupDetailsSheet({...@@ -232,7 +233,7 @@ function SetupDetailsSheet({
232 </View>233 </View>
233 ))}234 ))}
234 {snapshot.phase === "failed" && snapshot.error ? (235 {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">
236 {snapshot.error}237 {snapshot.error}
237 </Text>238 </Text>
238 ) : null}239 ) : null}
...@@ -303,7 +304,7 @@ function StageRow({...@@ -303,7 +304,7 @@ function StageRow({
303 {stage.status === "running" && animate ? (304 {stage.status === "running" && animate ? (
304 <ActivityIndicator305 <ActivityIndicator
305 size="small"306 size="small"
306 colorClassName="accent-adaptive-neutral-500-400"307 colorClassName="accent-icon-muted"
307 style={{ transform: [{ scale: 0.75 }] }}308 style={{ transform: [{ scale: 0.75 }] }}
308 />309 />
309 ) : (310 ) : (
...@@ -312,10 +313,10 @@ function StageRow({...@@ -312,10 +313,10 @@ function StageRow({
312 size={14}313 size={14}
313 tintColorClassName={314 tintColorClassName={
314 stage.status === "failed"315 stage.status === "failed"
315 ? "accent-adaptive-rose-700-300"316 ? "accent-danger-foreground"
316 : stage.status === "warning"317 : stage.status === "warning"
317 ? "accent-warning-foreground"318 ? "accent-warning-foreground"
318 : "accent-adaptive-neutral-500-400"319 : "accent-icon-muted"
319 }320 }
320 />321 />
321 )}322 )}
...@@ -323,7 +324,7 @@ function StageRow({...@@ -323,7 +324,7 @@ function StageRow({
323 {stage.status === "running" && animate ? (324 {stage.status === "running" && animate ? (
324 <ShimmeringWorkContent325 <ShimmeringWorkContent
325 icon="clock"326 icon="clock"
326 iconSubtleColor="#737373"327 iconSubtleColor="transparent"
327 label={label}328 label={label}
328 showIcon={false}329 showIcon={false}
329 />330 />
...@@ -332,24 +333,21 @@ function StageRow({...@@ -332,24 +333,21 @@ function StageRow({
332 numberOfLines={1}333 numberOfLines={1}
333 className={334 className={
334 stage.status === "failed"335 stage.status === "failed"
335 ? "flex-1 text-sm text-adaptive-rose-700-300"336 ? "flex-1 text-sm text-danger-foreground"
336 : "flex-1 text-sm text-adaptive-neutral-600-400"337 : "flex-1 text-sm text-foreground-secondary"
337 }338 }
338 >339 >
339 {label}340 {label}
340 </Text>341 </Text>
341 )}342 )}
342 {detail ? (343 {detail ? (
343 <Text344 <Text numberOfLines={1} className="max-w-[25%] shrink text-2xs text-foreground-secondary">
344 numberOfLines={1}
345 className="max-w-[25%] shrink text-2xs text-adaptive-neutral-600-400"
346 >
347 {detail}345 {detail}
348 </Text>346 </Text>
349 ) : null}347 ) : null}
350 {stage.status !== "pending" && stage.status !== "skipped" ? (348 {stage.status !== "pending" && stage.status !== "skipped" ? (
351 <Text349 <Text
352 className="text-2xs text-adaptive-neutral-600-400"350 className="text-2xs text-foreground-secondary"
353 style={{ fontVariant: ["tabular-nums"] }}351 style={{ fontVariant: ["tabular-nums"] }}
354 >352 >
355 {elapsed(stage.startedAt, stage.endedAt, now)}353 {elapsed(stage.startedAt, stage.endedAt, now)}
...@@ -368,8 +366,8 @@ function OutputTail({ lines, failed }: { lines: ReadonlyArray<string>; failed: b...@@ -368,8 +366,8 @@ function OutputTail({ lines, failed }: { lines: ReadonlyArray<string>; failed: b
368 accessibilityLabel="Setup script output"366 accessibilityLabel="Setup script output"
369 className={367 className={
370 failed368 failed
371 ? "mb-2 ml-8 rounded-md border border-adaptive-rose-700-300/20 bg-adaptive-rose-50-950 px-3 py-2"369 ? "mb-2 ml-8 rounded-md border border-danger-border bg-danger px-3 py-2"
372 : "mb-2 ml-8 rounded-md border border-border bg-adaptive-neutral-100-900 px-3 py-2"370 : "mb-2 ml-8 rounded-md border border-border bg-card-alt px-3 py-2"
373 }371 }
374 >372 >
375 {OUTPUT_TAIL_SLOTS.map((slot) => (373 {OUTPUT_TAIL_SLOTS.map((slot) => (
...@@ -379,8 +377,8 @@ function OutputTail({ lines, failed }: { lines: ReadonlyArray<string>; failed: b...@@ -379,8 +377,8 @@ function OutputTail({ lines, failed }: { lines: ReadonlyArray<string>; failed: b
379 numberOfLines={1}377 numberOfLines={1}
380 className={378 className={
381 failed379 failed
382 ? "text-2xs leading-5 text-adaptive-rose-700-300 ios:font-[family-name:Menlo] android:font-mono"380 ? "text-2xs leading-5 text-danger-foreground 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"381 : "text-2xs leading-5 text-foreground-secondary ios:font-[family-name:Menlo] android:font-mono"
384 }382 }
385 >383 >
386 {lines[lines.length - OUTPUT_TAIL_SLOTS.length + slot] || "\u00a0"}384 {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: {...@@ -300,7 +300,7 @@ export function ComposerDictationStatus(props: {
300 <View className="relative h-11 min-w-0 flex-1 justify-center">300 <View className="relative h-11 min-w-0 flex-1 justify-center">
301 {isError ? (301 {isError ? (
302 <View className="min-w-0 flex-row items-center gap-1.5 px-2">302 <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}>
304 {props.presentation.statusLabel}304 {props.presentation.statusLabel}
305 </Text>305 </Text>
306 <Pressable306 <Pressable
apps/mobile/src/lib/materialYouTheme.android.ts+12
...@@ -36,12 +36,15 @@ export function materialYouPaletteToMobileThemeVariables(...@@ -36,12 +36,15 @@ export function materialYouPaletteToMobileThemeVariables(
36 "--color-sheet": withAlpha(palette.surfaceContainerLow, 0.98),36 "--color-sheet": withAlpha(palette.surfaceContainerLow, 0.98),
37 "--color-sheet-solid": palette.surfaceContainerLow,37 "--color-sheet-solid": palette.surfaceContainerLow,
38 "--color-card": palette.surfaceContainer,38 "--color-card": palette.surfaceContainer,
39 "--color-grouped-card": palette.surfaceContainer,
39 "--color-card-alt": palette.surfaceContainerHigh,40 "--color-card-alt": palette.surfaceContainerHigh,
40 "--color-card-translucent": withAlpha(palette.surfaceContainer, 0.8),41 "--color-card-translucent": withAlpha(palette.surfaceContainer, 0.8),
41 "--color-thread-canvas": palette.surfaceContainerLow,42 "--color-thread-canvas": palette.surfaceContainerLow,
42 "--color-thread-selected": palette.surfaceContainer,43 "--color-thread-selected": palette.surfaceContainer,
43 "--color-thread-selected-foreground": palette.onSurface,44 "--color-thread-selected-foreground": palette.onSurface,
44 "--color-thread-selected-foreground-muted": palette.onSurfaceVariant,45 "--color-thread-selected-foreground-muted": palette.onSurfaceVariant,
46 "--color-thread-hover": palette.surfaceContainerHigh,
47 "--color-row-hover": palette.surfaceContainerHigh,
45 "--color-composer-panel": withAlpha(palette.surfaceContainerLow, dark ? 0.92 : 0.88),48 "--color-composer-panel": withAlpha(palette.surfaceContainerLow, dark ? 0.92 : 0.88),
46 "--color-composer-surface": withAlpha(palette.surfaceContainerHigh, dark ? 0.9 : 0.94),49 "--color-composer-surface": withAlpha(palette.surfaceContainerHigh, dark ? 0.9 : 0.94),
47 "--color-composer-border": withAlpha(palette.outlineVariant, dark ? 0.34 : 0.46),50 "--color-composer-border": withAlpha(palette.outlineVariant, dark ? 0.34 : 0.46),
...@@ -50,6 +53,7 @@ export function materialYouPaletteToMobileThemeVariables(...@@ -50,6 +53,7 @@ export function materialYouPaletteToMobileThemeVariables(
50 "--color-foreground-muted": palette.onSurfaceVariant,53 "--color-foreground-muted": palette.onSurfaceVariant,
51 "--color-foreground-tertiary": withAlpha(palette.onSurfaceVariant, 0.58),54 "--color-foreground-tertiary": withAlpha(palette.onSurfaceVariant, 0.58),
52 "--color-border": withAlpha(palette.outlineVariant, dark ? 0.5 : 0.65),55 "--color-border": withAlpha(palette.outlineVariant, dark ? 0.5 : 0.65),
56 "--color-focus": palette.primary,
53 "--color-border-subtle": withAlpha(palette.outlineVariant, dark ? 0.34 : 0.46),57 "--color-border-subtle": withAlpha(palette.outlineVariant, dark ? 0.34 : 0.46),
54 "--color-separator": withAlpha(palette.outlineVariant, dark ? 0.24 : 0.34),58 "--color-separator": withAlpha(palette.outlineVariant, dark ? 0.24 : 0.34),
55 "--color-subtle": withAlpha(palette.onSurface, 0.05),59 "--color-subtle": withAlpha(palette.onSurface, 0.05),
...@@ -59,6 +63,7 @@ export function materialYouPaletteToMobileThemeVariables(...@@ -59,6 +63,7 @@ export function materialYouPaletteToMobileThemeVariables(
59 "--color-inline-skill-foreground": palette.onTertiaryContainer,63 "--color-inline-skill-foreground": palette.onTertiaryContainer,
60 "--color-primary": palette.primary,64 "--color-primary": palette.primary,
61 "--color-primary-foreground": palette.onPrimary,65 "--color-primary-foreground": palette.onPrimary,
66 "--color-primary-text": palette.primary,
62 "--color-primary-shadow": withAlpha(palette.scrim, dark ? 0.22 : 0.18),67 "--color-primary-shadow": withAlpha(palette.scrim, dark ? 0.22 : 0.18),
63 "--color-secondary": palette.secondaryContainer,68 "--color-secondary": palette.secondaryContainer,
64 "--color-secondary-foreground": palette.onSecondaryContainer,69 "--color-secondary-foreground": palette.onSecondaryContainer,
...@@ -70,6 +75,8 @@ export function materialYouPaletteToMobileThemeVariables(...@@ -70,6 +75,8 @@ export function materialYouPaletteToMobileThemeVariables(
70 "--color-danger": palette.errorContainer,75 "--color-danger": palette.errorContainer,
71 "--color-danger-border": withAlpha(palette.onErrorContainer, dark ? 0.42 : 0.5),76 "--color-danger-border": withAlpha(palette.onErrorContainer, dark ? 0.42 : 0.5),
72 "--color-danger-foreground": palette.onErrorContainer,77 "--color-danger-foreground": palette.onErrorContainer,
78 "--color-update": palette.primaryContainer,
79 "--color-update-foreground": palette.onPrimaryContainer,
73 "--color-input": palette.surfaceContainerLowest,80 "--color-input": palette.surfaceContainerLowest,
74 "--color-input-border": withAlpha(palette.outline, dark ? 0.42 : 0.5),81 "--color-input-border": withAlpha(palette.outline, dark ? 0.42 : 0.5),
75 "--color-sidebar-search": palette.surfaceContainerLowest,82 "--color-sidebar-search": palette.surfaceContainerLowest,
...@@ -78,8 +85,10 @@ export function materialYouPaletteToMobileThemeVariables(...@@ -78,8 +85,10 @@ export function materialYouPaletteToMobileThemeVariables(
78 "--color-icon-muted": palette.onSurfaceVariant,85 "--color-icon-muted": palette.onSurfaceVariant,
79 "--color-icon-subtle": withAlpha(palette.onSurfaceVariant, 0.68),86 "--color-icon-subtle": withAlpha(palette.onSurfaceVariant, 0.68),
80 "--color-header": palette.surfaceContainerHigh,87 "--color-header": palette.surfaceContainerHigh,
88 "--color-header-foreground": palette.onSurface,
81 "--color-header-border": withAlpha(palette.outlineVariant, dark ? 0.36 : 0.48),89 "--color-header-border": withAlpha(palette.outlineVariant, dark ? 0.36 : 0.48),
82 "--color-glass-surface": withAlpha(palette.surfaceContainerHigh, dark ? 0.78 : 0.72),90 "--color-glass-surface": withAlpha(palette.surfaceContainerHigh, dark ? 0.78 : 0.72),
91 "--color-glass-fallback": withAlpha(palette.surfaceContainerHigh, dark ? 0.9 : 0.94),
83 "--color-glass-tint": withAlpha(palette.primaryContainer, dark ? 0.24 : 0.18),92 "--color-glass-tint": withAlpha(palette.primaryContainer, dark ? 0.24 : 0.18),
84 "--color-status-bar": palette.surfaceContainerLow,93 "--color-status-bar": palette.surfaceContainerLow,
85 "--color-md-body": materialTone(palette.onSurfaceVariant, dark ? 90 : 10),94 "--color-md-body": materialTone(palette.onSurfaceVariant, dark ? 90 : 10),
...@@ -100,6 +109,9 @@ export function materialYouPaletteToMobileThemeVariables(...@@ -100,6 +109,9 @@ export function materialYouPaletteToMobileThemeVariables(
100 "--color-user-bubble-skill-foreground": userBubbleForeground,109 "--color-user-bubble-skill-foreground": userBubbleForeground,
101 "--color-backdrop": withAlpha(palette.scrim, dark ? 0.48 : 0.22),110 "--color-backdrop": withAlpha(palette.scrim, dark ? 0.48 : 0.22),
102 "--color-drawer": palette.surface,111 "--color-drawer": palette.surface,
112 "--color-drawer-foreground": palette.onSurface,
113 "--color-drawer-foreground-muted": palette.onSurfaceVariant,
114 "--color-drawer-border": withAlpha(palette.outlineVariant, dark ? 0.36 : 0.48),
103 "--color-drawer-shadow": withAlpha(palette.scrim, dark ? 0.32 : 0.12),115 "--color-drawer-shadow": withAlpha(palette.scrim, dark ? 0.32 : 0.12),
104 "--color-dot-separator": withAlpha(palette.onSurface, 0.2),116 "--color-dot-separator": withAlpha(palette.onSurface, 0.2),
105 "--color-wordmark": palette.onSurface,117 "--color-wordmark": palette.onSurface,
apps/mobile/src/lib/materialYouTheme.test.ts+1
...@@ -54,6 +54,7 @@ describe("Material You system colors", () => {...@@ -54,6 +54,7 @@ describe("Material You system colors", () => {
54 expect(variables["--color-thread-canvas"]).toBe(palette.surfaceContainerLow);54 expect(variables["--color-thread-canvas"]).toBe(palette.surfaceContainerLow);
55 expect(variables["--color-thread-selected"]).toBe(palette.surfaceContainer);55 expect(variables["--color-thread-selected"]).toBe(palette.surfaceContainer);
56 expect(variables["--color-header"]).toBe(palette.surfaceContainerHigh);56 expect(variables["--color-header"]).toBe(palette.surfaceContainerHigh);
57 expect(variables["--color-grouped-card"]).toBe(palette.surfaceContainer);
57 expect(variables["--color-primary"]).toBe(palette.primary);58 expect(variables["--color-primary"]).toBe(palette.primary);
58 expect(variables["--color-placeholder"]).toBe("#1C1B1F9E");59 expect(variables["--color-placeholder"]).toBe("#1C1B1F9E");
59 });60 });
apps/mobile/src/lib/mobileTheme.test-support.ts+13-4
...@@ -1,20 +1,29 @@...@@ -1,20 +1,29 @@
1import * as NodeFS from "node:fs";1import * 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
5export function readDefaultMobileThemeVariables(9export function readDefaultMobileThemeVariables(
6 appearance: MobileThemeAppearance,10 appearance: MobileThemeAppearance,
7): MobileThemeVariables {11): 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 );
9 const variant = new RegExp(`@variant ${appearance} \\{([\\s\\S]*?)\\n \\}`, "u").exec(16 const variant = new RegExp(`@variant ${appearance} \\{([\\s\\S]*?)\\n \\}`, "u").exec(
10 stylesheet,17 stylesheet,
11 )?.[1];18 )?.[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
14 return Object.fromEntries(21 return Object.fromEntries(
15 Array.from(variant.matchAll(/(--color-[a-z0-9-]+):\s*([^;]+);/gu), ([, name, value]) => [22 Array.from(variant.matchAll(/(--color-[a-z0-9-]+):\s*([^;]+);/gu), ([, name, value]) => [
16 name,23 name,
17 (value ?? "").trim(),24 (value ?? "").trim(),
18 ]),25 ]).filter(([name]) =>
26 MOBILE_THEME_VARIABLE_NAMES.includes(name as (typeof MOBILE_THEME_VARIABLE_NAMES)[number]),
27 ),
19 ) as MobileThemeVariables;28 ) as MobileThemeVariables;
20}29}
apps/mobile/src/lib/mobileTheme.test.ts+147-10
...@@ -1,12 +1,22 @@...@@ -1,12 +1,22 @@
1import { describe, expect, it } from "vite-plus/test";1import { 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";
3import { readDefaultMobileThemeVariables } from "./mobileTheme.test-support";11import { readDefaultMobileThemeVariables } from "./mobileTheme.test-support";
12import { getMobileThemeRuntimeVariables } from "./mobileThemeVariables";
413
5import {14import {
6 createMobileThemePairPatch,15 createMobileThemePairPatch,
7 createMobileThemeSelectionPatch,16 createMobileThemeSelectionPatch,
8 createMobileThemeVariables,17 createMobileThemeVariables,
9 DEFAULT_MOBILE_THEME_ID,18 DEFAULT_MOBILE_THEME_ID,
19 flattenThemeColor,
10 getMobileThemePreviewColors,20 getMobileThemePreviewColors,
11 getMobileThemeVariables,21 getMobileThemeVariables,
12 normalizeMobileThemeId,22 normalizeMobileThemeId,
...@@ -66,12 +76,86 @@ describe("mobile themes", () => {...@@ -66,12 +76,86 @@ describe("mobile themes", () => {
66 }76 }
67 });77 });
6878
69 it("preserves the existing mobile palette as the default", () => {79 it.each(MOBILE_THEME_IDS)("uses the web color roles for %s in both appearances", (themeId) => {
70 expect(readDefaultMobileThemeVariables("light")["--color-screen"]).toBe("#f2f2f7");80 for (const appearance of ["light", "dark"] as const) {
71 expect(readDefaultMobileThemeVariables("dark")["--color-screen"]).toBe("#0a0a0a");81 const theme = BUILT_IN_THEMES.find((candidate) => candidate.id === themeId);
72 expect(readDefaultMobileThemeVariables("light")["--color-user-bubble-skill-foreground"]).toBe(82 const colors = theme
73 "#2563eb",83 ? getThemeColorsForAppearance(theme, appearance)!
74 );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 }
75 });159 });
76160
77 it("applies palette overrides on top of the selected built-in theme", () => {161 it("applies palette overrides on top of the selected built-in theme", () => {
...@@ -83,6 +167,62 @@ describe("mobile themes", () => {...@@ -83,6 +167,62 @@ describe("mobile themes", () => {
83 expect(variables["--color-screen"]).toMatch(/^#/);167 expect(variables["--color-screen"]).toMatch(/^#/);
84 });168 });
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
86 it("uses the same preview roles and standard artwork as desktop", () => {226 it("uses the same preview roles and standard artwork as desktop", () => {
87 expect(getMobileThemePreviewColors(DEFAULT_MOBILE_THEME_ID, "light")).toEqual({227 expect(getMobileThemePreviewColors(DEFAULT_MOBILE_THEME_ID, "light")).toEqual({
88 canvas: "#fcfcfc",228 canvas: "#fcfcfc",
...@@ -153,7 +293,6 @@ describe("mobile themes", () => {...@@ -153,7 +293,6 @@ describe("mobile themes", () => {
153293
154 it("maps semantic palette roles onto every mobile color variable", () => {294 it("maps semantic palette roles onto every mobile color variable", () => {
155 const variables = createMobileThemeVariables(T3_CHAT_THEME.colors, "light");295 const variables = createMobileThemeVariables(T3_CHAT_THEME.colors, "light");
156 expect(Object.keys(variables)).toHaveLength(75);
157 expect(variables["--color-sheet-solid"]).toBe(296 expect(variables["--color-sheet-solid"]).toBe(
158 themeColorToNativeColor(T3_CHAT_THEME.colors.chrome),297 themeColorToNativeColor(T3_CHAT_THEME.colors.chrome),
159 );298 );
...@@ -219,8 +358,6 @@ describe("mobile themes", () => {...@@ -219,8 +358,6 @@ describe("mobile themes", () => {
219 }358 }
220 });359 });
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.
224 it("keeps the default user bubble readable in both appearances", () => {361 it("keeps the default user bubble readable in both appearances", () => {
225 for (const appearance of ["light", "dark"] as const) {362 for (const appearance of ["light", "dark"] as const) {
226 const variables = readDefaultMobileThemeVariables(appearance);363 const variables = readDefaultMobileThemeVariables(appearance);
apps/mobile/src/lib/mobileTheme.ts+106-43
...@@ -1,10 +1,11 @@...@@ -1,10 +1,11 @@
1import {1import {
2 BUILT_IN_THEMES,2 BUILT_IN_THEMES,
3 T3_CHAT_THEME,3 T3_CHAT_THEME,
4 T3_CODE_LIGHT_THEME_COLORS,
5 T3_CODE_DARK_THEME_COLORS,
4 getThemeColorsForAppearance,6 getThemeColorsForAppearance,
5 MOBILE_DEFAULT_THEME_ID,7 MOBILE_DEFAULT_THEME_ID,
6 MOBILE_THEME_IDS as SHARED_MOBILE_THEME_IDS,8 MOBILE_THEME_IDS as SHARED_MOBILE_THEME_IDS,
7 type BuiltInThemeId,
8 type MobileThemeId as SharedMobileThemeId,9 type MobileThemeId as SharedMobileThemeId,
9 type ThemeAppearance,10 type ThemeAppearance,
10 type ThemeColors,11 type ThemeColors,
...@@ -138,7 +139,7 @@ function withAlpha(color: string, alpha: number): string {...@@ -138,7 +139,7 @@ function withAlpha(color: string, alpha: number): string {
138}139}
139140
140function rgbChannels(color: string): readonly [number, number, number] | null {141function 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);
142 if (!match) return null;143 if (!match) return null;
143 const [, red = "0", green = "0", blue = "0"] = match;144 const [, red = "0", green = "0", blue = "0"] = match;
144 return [Number.parseInt(red, 16), Number.parseInt(green, 16), Number.parseInt(blue, 16)];145 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 {...@@ -150,6 +151,14 @@ function rgbChannels(color: string): readonly [number, number, number] | null {
150 * colour asked for — so a translucent role like `--color-border` has to be flattened first.151 * colour asked for — so a translucent role like `--color-border` has to be flattened first.
151 */152 */
152export function flattenThemeColor(color: string, surface: string): string {153export 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 }
153 const match = /^rgba?\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)(?:[,\s/]+([\d.]+))?\s*\)$/i.exec(162 const match = /^rgba?\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)(?:[,\s/]+([\d.]+))?\s*\)$/i.exec(
154 color.trim(),163 color.trim(),
155 );164 );
...@@ -182,22 +191,21 @@ function contrastRatio(...@@ -182,22 +191,21 @@ function contrastRatio(
182 );191 );
183}192}
184193
185/** Preserve the theme's action hue while making it readable as skill text on a message bubble. */194/** Preserve the color's hue while giving text 4.5:1 contrast on every supplied surface. */
186function readableMessageAccent(accent: string, surface: string): string {195function readableTextColor(accent: string, surface: string | ReadonlyArray<string>): string {
187 const accentChannels = rgbChannels(accent);196 const accentChannels = rgbChannels(accent);
188 const surfaceChannels = rgbChannels(surface);197 const surfaceChannels = (typeof surface === "string" ? [surface] : surface)
189 if (198 .map(rgbChannels)
190 !accentChannels ||199 .filter((channels) => channels !== null);
191 !surfaceChannels ||200 const minimumContrast = (channels: readonly [number, number, number]) =>
192 contrastRatio(accentChannels, surfaceChannels) >= 4.5201 Math.min(...surfaceChannels.map((surface) => contrastRatio(channels, surface)));
193 ) {202 if (!accentChannels || surfaceChannels.length === 0 || minimumContrast(accentChannels) >= 4.5) {
194 return accent;203 return accent;
195 }204 }
196205
197 const black = [0, 0, 0] as const;206 const black = [0, 0, 0] as const;
198 const white = [255, 255, 255] as const;207 const white = [255, 255, 255] as const;
199 const target =208 const target = minimumContrast(black) >= minimumContrast(white) ? black : white;
200 contrastRatio(black, surfaceChannels) >= contrastRatio(white, surfaceChannels) ? black : white;
201 let readable: readonly [number, number, number] = target;209 let readable: readonly [number, number, number] = target;
202 let lowerAmount = 0;210 let lowerAmount = 0;
203 let upperAmount = 1;211 let upperAmount = 1;
...@@ -208,7 +216,7 @@ function readableMessageAccent(accent: string, surface: string): string {...@@ -208,7 +216,7 @@ function readableMessageAccent(accent: string, surface: string): string {
208 Math.round(accentChannels[1] + (target[1] - accentChannels[1]) * amount),216 Math.round(accentChannels[1] + (target[1] - accentChannels[1]) * amount),
209 Math.round(accentChannels[2] + (target[2] - accentChannels[2]) * amount),217 Math.round(accentChannels[2] + (target[2] - accentChannels[2]) * amount),
210 ];218 ];
211 if (contrastRatio(candidate, surfaceChannels) >= 4.5) {219 if (minimumContrast(candidate) >= 4.5) {
212 readable = candidate;220 readable = candidate;
213 upperAmount = amount;221 upperAmount = amount;
214 } else {222 } else {
...@@ -227,30 +235,46 @@ export function themeColorWithAlpha(color: string, alpha: number): string {...@@ -227,30 +235,46 @@ export function themeColorWithAlpha(color: string, alpha: number): string {
227 return rgb ? `rgba(${rgb[1]}, ${rgb[2]}, ${rgb[3]}, ${alpha})` : color;235 return rgb ? `rgba(${rgb[1]}, ${rgb[2]}, ${rgb[3]}, ${alpha})` : color;
228}236}
229237
230export function createMobileThemeVariables(colors: ThemeColors, appearance: MobileThemeAppearance) {238export function createMobileThemeVariables(
239 colors: ThemeColors,
240 appearance: MobileThemeAppearance,
241 groupedCardColor = colors.surface,
242) {
231 const c = nativeColors(colors);243 const c = nativeColors(colors);
244 const groupedCard = themeColorToNativeColor(groupedCardColor);
245 const textSurfaces = [c.canvas, c.surface, c.surfaceRaised, c.chrome, groupedCard];
232 return {246 return {
233 "--color-screen": c.canvas,247 "--color-screen": c.canvas,
234 "--color-sheet": withAlpha(c.chrome, 0.98),248 "--color-sheet": withAlpha(c.chrome, 0.98),
235 "--color-sheet-solid": c.chrome,249 "--color-sheet-solid": c.chrome,
236 "--color-card": c.surfaceRaised,250 "--color-card": c.surface,
237 "--color-card-alt": c.surface,251 "--color-grouped-card": groupedCard,
238 "--color-card-translucent": withAlpha(c.surfaceRaised, 0.8),252 "--color-card-alt": c.surfaceRaised,
239 "--color-thread-canvas": c.surface,253 "--color-card-translucent": withAlpha(c.surface, 0.8),
240 "--color-thread-selected": c.surfaceRaised,254 "--color-thread-canvas": c.canvas,
241 "--color-thread-selected-foreground": c.text,255 "--color-thread-selected": c.sidebarRowActive,
242 "--color-thread-selected-foreground-muted": c.textMuted,256 "--color-thread-selected-foreground": c.sidebarForeground,
243 "--color-composer-panel": themeColorWithAlpha(c.surface, appearance === "dark" ? 0.92 : 0.88),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),
244 "--color-composer-surface": themeColorWithAlpha(264 "--color-composer-surface": themeColorWithAlpha(
245 c.surfaceRaised,265 groupedCard,
246 appearance === "dark" ? 0.9 : 0.94,266 appearance === "dark" ? 0.9 : 0.94,
247 ),267 ),
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 ),
249 "--color-foreground": c.text,272 "--color-foreground": c.text,
250 "--color-foreground-secondary": c.textMuted,273 "--color-foreground-secondary": readableTextColor(c.textMuted, textSurfaces),
251 "--color-foreground-muted": c.mutedForeground,274 "--color-foreground-muted": readableTextColor(c.mutedForeground, textSurfaces),
252 "--color-foreground-tertiary": c.secondaryLabel,275 "--color-foreground-tertiary": c.secondaryLabel,
253 "--color-border": c.border,276 "--color-border": c.border,
277 "--color-focus": c.focus,
254 "--color-border-subtle": withAlpha(c.border, 0.7),278 "--color-border-subtle": withAlpha(c.border, 0.7),
255 "--color-separator": withAlpha(c.border, 0.55),279 "--color-separator": withAlpha(c.border, 0.55),
256 "--color-subtle": c.muted,280 "--color-subtle": c.muted,
...@@ -258,14 +282,15 @@ export function createMobileThemeVariables(colors: ThemeColors, appearance: Mobi...@@ -258,14 +282,15 @@ export function createMobileThemeVariables(colors: ThemeColors, appearance: Mobi
258 "--color-inline-skill-background": c.accentSurface,282 "--color-inline-skill-background": c.accentSurface,
259 "--color-inline-skill-border": withAlpha(c.accent, 0.42),283 "--color-inline-skill-border": withAlpha(c.accent, 0.42),
260 "--color-inline-skill-foreground": c.accentSurfaceForeground,284 "--color-inline-skill-foreground": c.accentSurfaceForeground,
261 "--color-primary": c.accent,285 "--color-primary": c.messageAction,
262 "--color-primary-foreground": c.accentForeground,286 "--color-primary-foreground": c.messageActionForeground,
287 "--color-primary-text": readableTextColor(c.messageAction, textSurfaces),
263 "--color-primary-shadow": "#000000",288 "--color-primary-shadow": "#000000",
264 "--color-secondary": c.secondary,289 "--color-secondary": c.secondary,
265 "--color-secondary-foreground": c.secondaryForeground,290 "--color-secondary-foreground": c.secondaryForeground,
266 "--color-secondary-border": c.border,291 "--color-secondary-border": c.border,
267 "--color-switch-active-track": c.accent,292 "--color-switch-active-track": c.messageAction,
268 "--color-switch-active-thumb": c.accentForeground,293 "--color-switch-active-thumb": c.messageActionForeground,
269 "--color-switch-inactive-track": c.secondary,294 "--color-switch-inactive-track": c.secondary,
270 "--color-switch-inactive-thumb": c.mutedForeground,295 "--color-switch-inactive-thumb": c.mutedForeground,
271 "--color-warning": c.warningSurface,296 "--color-warning": c.warningSurface,
...@@ -274,21 +299,25 @@ export function createMobileThemeVariables(colors: ThemeColors, appearance: Mobi...@@ -274,21 +299,25 @@ export function createMobileThemeVariables(colors: ThemeColors, appearance: Mobi
274 "--color-danger": c.errorSurface,299 "--color-danger": c.errorSurface,
275 "--color-danger-border": withAlpha(c.error, 0.32),300 "--color-danger-border": withAlpha(c.error, 0.32),
276 "--color-danger-foreground": c.errorForeground,301 "--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,
278 "--color-input-border": c.input,305 "--color-input-border": c.input,
279 "--color-sidebar-search": c.sidebarControlSurface,306 "--color-sidebar-search": c.sidebarControlSurface,
280 "--color-placeholder": c.placeholder,307 "--color-placeholder": readableTextColor(c.placeholder, textSurfaces),
281 "--color-icon": c.text,308 "--color-icon": c.text,
282 "--color-icon-muted": c.iconMuted,309 "--color-icon-muted": c.iconMuted,
283 "--color-icon-subtle": c.secondaryLabel,310 "--color-icon-subtle": c.secondaryLabel,
284 "--color-header": withAlpha(c.toolbar, 0.97),311 "--color-header": withAlpha(c.toolbar, 0.97),
312 "--color-header-foreground": c.toolbarForeground,
285 "--color-header-border": c.toolbarBorder,313 "--color-header-border": c.toolbarBorder,
286 "--color-glass-surface": withAlpha(c.surfaceOverlay, 0.74),314 "--color-glass-surface": withAlpha(c.surfaceOverlay, 0.74),
315 "--color-glass-fallback": themeColorWithAlpha(groupedCard, appearance === "dark" ? 0.9 : 0.94),
287 "--color-glass-tint": withAlpha(c.surfaceOverlay, 0.22),316 "--color-glass-tint": withAlpha(c.surfaceOverlay, 0.22),
288 "--color-status-bar": c.canvas,317 "--color-status-bar": c.canvas,
289 "--color-md-body": c.text,318 "--color-md-body": c.text,
290 "--color-md-strong": c.toolbarForeground,319 "--color-md-strong": c.text,
291 "--color-md-link": c.accent,320 "--color-md-link": readableTextColor(c.messageAction, c.canvas),
292 "--color-md-blockquote-border": c.border,321 "--color-md-blockquote-border": c.border,
293 "--color-md-blockquote-bg": c.muted,322 "--color-md-blockquote-bg": c.muted,
294 "--color-md-code-bg": c.codeBackground,323 "--color-md-code-bg": c.codeBackground,
...@@ -301,12 +330,15 @@ export function createMobileThemeVariables(colors: ThemeColors, appearance: Mobi...@@ -301,12 +330,15 @@ export function createMobileThemeVariables(colors: ThemeColors, appearance: Mobi
301 "--color-user-bubble": c.messageSurface,330 "--color-user-bubble": c.messageSurface,
302 "--color-user-bubble-foreground": c.messageForeground,331 "--color-user-bubble-foreground": c.messageForeground,
303 "--color-user-bubble-foreground-muted": withAlpha(c.messageForeground, 0.78),332 "--color-user-bubble-foreground-muted": withAlpha(c.messageForeground, 0.78),
304 "--color-user-bubble-skill-foreground": readableMessageAccent(333 "--color-user-bubble-skill-foreground": readableTextColor(c.messageAction, c.messageSurface),
305 c.messageAction,
306 c.messageSurface,
307 ),
308 "--color-backdrop": withAlpha("#000000", appearance === "dark" ? 0.48 : 0.22),334 "--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,
310 "--color-drawer-shadow": withAlpha("#000000", appearance === "dark" ? 0.32 : 0.12),342 "--color-drawer-shadow": withAlpha("#000000", appearance === "dark" ? 0.32 : 0.12),
311 "--color-dot-separator": withAlpha(c.textMuted, 0.35),343 "--color-dot-separator": withAlpha(c.textMuted, 0.35),
312 "--color-wordmark": c.text,344 "--color-wordmark": c.text,
...@@ -318,14 +350,45 @@ export const MOBILE_THEME_VARIABLE_NAMES = Object.keys(...@@ -318,14 +350,45 @@ export const MOBILE_THEME_VARIABLE_NAMES = Object.keys(
318 createMobileThemeVariables(T3_CHAT_THEME.colors, "light"),350 createMobileThemeVariables(T3_CHAT_THEME.colors, "light"),
319) as ReadonlyArray<MobileThemeVariable>;351) 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
321export function getMobileThemeVariables(364export function getMobileThemeVariables(
322 themeId: BuiltInThemeId,365 themeId: SharedMobileThemeId,
323 appearance: MobileThemeAppearance,366 appearance: MobileThemeAppearance,
324 overrides: Partial<MobileThemeVariables> | null = null,367 overrides: Partial<MobileThemeVariables> | null = null,
325): MobileThemeVariables {368): MobileThemeVariables {
326 const theme = BUILT_IN_THEMES.find((candidate) => candidate.id === themeId) ?? T3_CHAT_THEME;369 const colors = getMobileThemeColors(themeId, appearance);
327 const colors = getThemeColorsForAppearance(theme, appearance) ?? theme.colors;370 // Mobile settings groups and fallback materials use tonal fills where desktop
328 const baseVariables = createMobileThemeVariables(colors, appearance);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
330 // The complete base record guarantees that optional overrides cannot leave a token undefined.393 // The complete base record guarantees that optional overrides cannot leave a token undefined.
331 return overrides ? ({ ...baseVariables, ...overrides } as MobileThemeVariables) : baseVariables;394 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 "...@@ -5,11 +5,11 @@ import { getMobileThemeVariables, MOBILE_THEME_IDS, themeColorWithAlpha } from "
5import { getMobileThemeRuntimeVariables } from "./mobileThemeVariables";5import { getMobileThemeRuntimeVariables } from "./mobileThemeVariables";
66
7describe("mobile theme runtime variables", () => {7describe("mobile theme runtime variables", () => {
8 it("derives the standard runtime palette from global.css", () => {8 it("matches the standard base palette to the generated stylesheet", () => {
9 expect(getMobileThemeRuntimeVariables("t3-code", "light", "ios")).toEqual(9 expect(getMobileThemeRuntimeVariables("t3-code", "light", "web")).toEqual(
10 readDefaultMobileThemeVariables("light"),10 readDefaultMobileThemeVariables("light"),
11 );11 );
12 expect(getMobileThemeRuntimeVariables("t3-code", "dark", "ios")).toEqual(12 expect(getMobileThemeRuntimeVariables("t3-code", "dark", "web")).toEqual(
13 readDefaultMobileThemeVariables("dark"),13 readDefaultMobileThemeVariables("dark"),
14 );14 );
15 });15 });
...@@ -27,18 +27,22 @@ describe("mobile theme runtime variables", () => {...@@ -27,18 +27,22 @@ describe("mobile theme runtime variables", () => {
27 "keeps %s colors on Android with an opaque Material frame",27 "keeps %s colors on Android with an opaque Material frame",
28 (themeId) => {28 (themeId) => {
29 for (const appearance of ["light", "dark"] as const) {29 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 );
31 const android = getMobileThemeRuntimeVariables(themeId, appearance, "android");34 const android = getMobileThemeRuntimeVariables(themeId, appearance, "android");
32 expect(android).toEqual({35 expect(android).toEqual({
33 ...ios,36 ...base,
34 "--color-header": themeColorWithAlpha(37 "--color-header": themeColorWithAlpha(
35 ios[38 base[
36 themeId === "t3-code" || themeId === "material-you"39 themeId === "t3-code" || themeId === "material-you"
37 ? "--color-card"40 ? "--color-row-hover"
38 : "--color-drawer"41 : "--color-drawer"
39 ],42 ],
40 1,43 1,
41 ),44 ),
45 "--color-header-foreground": base["--color-drawer-foreground"],
42 });46 });
43 expect(android["--color-header"]).toMatch(/^rgba\(\d+, \d+, \d+, 1\)$/);47 expect(android["--color-header"]).toMatch(/^rgba\(\d+, \d+, \d+, 1\)$/);
44 }48 }
...@@ -46,13 +50,52 @@ describe("mobile theme runtime variables", () => {...@@ -46,13 +50,52 @@ describe("mobile theme runtime variables", () => {
46 );50 );
4751
48 it.each(["t3-code", "material-you"] as const)(52 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",
50 (themeId) => {69 (themeId) => {
51 const variables = getMobileThemeRuntimeVariables(themeId, "dark", "android");70 for (const appearance of ["light", "dark"] as const) {
52 expect(variables["--color-header"]).toBe("rgba(23, 23, 23, 1)");71 const ios = getMobileThemeRuntimeVariables(themeId, appearance, "ios");
53 expect(variables["--color-header"]).not.toBe(72 const android = getMobileThemeRuntimeVariables(themeId, appearance, "android");
54 themeColorWithAlpha(variables["--color-sheet-solid"], 1),73 if (appearance === "light") {
55 );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 }
56 },99 },
57 );100 );
58});101});
apps/mobile/src/lib/mobileThemeVariables.ts+20-9
...@@ -14,8 +14,8 @@ const defaults = defaultThemeVariables as Readonly<...@@ -14,8 +14,8 @@ const defaults = defaultThemeVariables as Readonly<
1414
15/**15/**
16 * Complete palette for native and third-party APIs that cannot consume a16 * Complete palette for native and third-party APIs that cannot consume a
17 * Uniwind className. The standard palette is generated from global.css; custom17 * Uniwind className. Every palette shares the source that generates its
18 * palettes share the same source that generates their registered CSS themes.18 * registered CSS theme.
19 */19 */
20export function getMobileThemeRuntimeVariables(20export function getMobileThemeRuntimeVariables(
21 themeId: MobileThemeId,21 themeId: MobileThemeId,
...@@ -26,16 +26,27 @@ export function getMobileThemeRuntimeVariables(...@@ -26,16 +26,27 @@ export function getMobileThemeRuntimeVariables(
26 const variables = usesDefaultPalette26 const variables = usesDefaultPalette
27 ? defaults[appearance]27 ? defaults[appearance]
28 : getMobileThemeVariables(themeId, appearance);28 : 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 }
29 if (platform !== "android") return variables;45 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.
34 return {47 return {
35 ...variables,48 ...variables,
36 "--color-header": themeColorWithAlpha(49 "--color-header": frame,
37 variables[usesDefaultPalette ? "--color-card" : "--color-drawer"],50 "--color-header-foreground": variables["--color-drawer-foreground"],
38 1,
39 ),
40 };51 };
41}52}
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";...@@ -10,7 +10,7 @@ import { useUniwindTheme } from "./useUniwindTheme";
10 * preferences provider applies the registered Uniwind theme first, then10 * preferences provider applies the registered Uniwind theme first, then
11 * publishes this matching navigation palette through React.11 * publishes this matching navigation palette through React.
12 */12 */
13export function useMobileNavigationTheme(): Theme {13export function useMobileNavigationTheme(surface: "screen" | "sidebar" = "screen"): Theme {
14 const { themeAppearance: appearance } = useAppearancePreferences();14 const { themeAppearance: appearance } = useAppearancePreferences();
15 const variables = useUniwindTheme();15 const variables = useUniwindTheme();
16 return useMemo(() => {16 return useMemo(() => {
...@@ -19,13 +19,16 @@ export function useMobileNavigationTheme(): Theme {...@@ -19,13 +19,16 @@ export function useMobileNavigationTheme(): Theme {
19 ...base,19 ...base,
20 colors: {20 colors: {
21 ...base.colors,21 ...base.colors,
22 primary: variables["--color-primary"],22 primary: variables["--color-primary-text"],
23 background: variables["--color-screen"],23 background: variables[surface === "sidebar" ? "--color-drawer" : "--color-screen"],
24 card: variables["--color-sheet-solid"],24 card: variables[surface === "sidebar" ? "--color-drawer" : "--color-sheet-solid"],
25 text: variables["--color-foreground"],25 text: variables[
26 border: variables["--color-header-border"],26 surface === "sidebar" ? "--color-drawer-foreground" : "--color-header-foreground"
27 ],
28 border:
29 variables[surface === "sidebar" ? "--color-drawer-border" : "--color-header-border"],
27 notification: variables["--color-danger-foreground"],30 notification: variables["--color-danger-foreground"],
28 },31 },
29 };32 };
30 }, [appearance, variables]);33 }, [appearance, surface, variables]);
31}34}
apps/mobile/src/native/SelectableMarkdownText.android.tsx+2-2
...@@ -24,8 +24,8 @@ export function hasNativeSelectableMarkdownText(): boolean {...@@ -24,8 +24,8 @@ export function hasNativeSelectableMarkdownText(): boolean {
2424
25export function SelectableMarkdownText(props: MobileSelectableMarkdownTextProps) {25export function SelectableMarkdownText(props: MobileSelectableMarkdownTextProps) {
26 const theme = useUniwindTheme();26 const theme = useUniwindTheme();
27 const selectionColor = themeColorWithAlpha(theme["--color-primary"], 0.32);27 const selectionColor = themeColorWithAlpha(theme["--color-focus"], 0.32);
28 const selectionHandleColor = theme["--color-primary"];28 const selectionHandleColor = theme["--color-focus"];
29 const textStyle = useMemo(29 const textStyle = useMemo(
30 () => ({ selectionColor, selectionHandleColor, ...props.textStyle }),30 () => ({ selectionColor, selectionHandleColor, ...props.textStyle }),
31 [props.textStyle, selectionColor, selectionHandleColor],31 [props.textStyle, selectionColor, selectionHandleColor],
apps/mobile/src/native/T3ComposerEditor.ios.tsx+2-13
...@@ -22,7 +22,7 @@ import {...@@ -22,7 +22,7 @@ import {
22} from "@t3tools/mobile-markdown-text/markdown";22} from "@t3tools/mobile-markdown-text/markdown";
23import { resolveMarkdownFileIcon } from "@t3tools/mobile-markdown-text/links";23import { resolveMarkdownFileIcon } from "@t3tools/mobile-markdown-text/links";
24import { useUniwindTheme } from "../lib/useUniwindTheme";24import { useUniwindTheme } from "../lib/useUniwindTheme";
25import { flattenThemeColor } from "../lib/mobileTheme";25import { createNativeComposerTheme } from "../lib/nativeComposerTheme";
26import { useFontFamily } from "../lib/useFontFamily";26import { useFontFamily } from "../lib/useFontFamily";
27import { useScaledTextRole } from "../features/settings/appearance/useScaledTextRole";27import { useScaledTextRole } from "../features/settings/appearance/useScaledTextRole";
28import {28import {
...@@ -257,18 +257,7 @@ export function ComposerEditor({...@@ -257,18 +257,7 @@ export function ComposerEditor({
257 },257 },
258 [],258 [],
259 );259 );
260 const themeJson = JSON.stringify({260 const themeJson = JSON.stringify(createNativeComposerTheme(theme));
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 });
272 const resolvedTextStyle = StyleSheet.flatten(textStyle) ?? {};261 const resolvedTextStyle = StyleSheet.flatten(textStyle) ?? {};
273 return (262 return (
274 <NativeView263 <NativeView
apps/mobile/src/native/T3ComposerEditor.native.tsx+3-12
...@@ -27,7 +27,7 @@ import { useNativePaste } from "../lib/useNativePaste";...@@ -27,7 +27,7 @@ import { useNativePaste } from "../lib/useNativePaste";
27import { useFontFamily } from "../lib/useFontFamily";27import { useFontFamily } from "../lib/useFontFamily";
28import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider";28import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider";
29import { useUniwindTheme } from "../lib/useUniwindTheme";29import { useUniwindTheme } from "../lib/useUniwindTheme";
30import { flattenThemeColor } from "../lib/mobileTheme";30import { createNativeComposerTheme } from "../lib/nativeComposerTheme";
31import {31import {
32 acknowledgeComposerNativeEvent,32 acknowledgeComposerNativeEvent,
33 assumeComposerControlledState,33 assumeComposerControlledState,
...@@ -259,17 +259,8 @@ export function ComposerEditor({...@@ -259,17 +259,8 @@ export function ComposerEditor({
259 );259 );
260 const { systemColorsActive } = useAppearancePreferences();260 const { systemColorsActive } = useAppearancePreferences();
261 const themeJson = JSON.stringify({261 const themeJson = JSON.stringify({
262 selection: Platform.OS === "android" || systemColorsActive ? theme["--color-primary"] : null,262 ...createNativeComposerTheme(theme),
263 text: theme["--color-foreground"],263 selection: Platform.OS === "android" || systemColorsActive ? theme["--color-focus"] : null,
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"],
273 });264 });
274 const resolvedTextStyle = StyleSheet.flatten(textStyle) ?? {};265 const resolvedTextStyle = StyleSheet.flatten(textStyle) ?? {};
275 const regularFontFamily = useFontFamily("regular");266 const regularFontFamily = useFontFamily("regular");
apps/web/src/themePalette.test.ts+15
...@@ -103,6 +103,21 @@ describe("theme files", () => {...@@ -103,6 +103,21 @@ describe("theme files", () => {
103 });103 });
104 });104 });
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
106 it("derives readable, distinctive vivid palettes from exact seeds", () => {121 it("derives readable, distinctive vivid palettes from exact seeds", () => {
107 const seeds: ReadonlyArray<["light" | "dark", string, string]> = [122 const seeds: ReadonlyArray<["light" | "dark", string, string]> = [
108 ["light", "#f4f9f2", "#1d8a4e"],123 ["light", "#f4f9f2", "#1d8a4e"],
apps/web/src/themePalette.ts+2-127
...@@ -9,6 +9,8 @@ import {...@@ -9,6 +9,8 @@ import {
9 IRIS_THEME,9 IRIS_THEME,
10 OCEAN_THEME,10 OCEAN_THEME,
11 T3_CHAT_THEME,11 T3_CHAT_THEME,
12 T3_CODE_LIGHT_THEME_COLORS,
13 T3_CODE_DARK_THEME_COLORS,
12 RESERVED_THEME_IDS,14 RESERVED_THEME_IDS,
13 THEME_COLOR_ROLES,15 THEME_COLOR_ROLES,
14 type ThemeAppearance,16 type ThemeAppearance,
...@@ -326,133 +328,6 @@ function legacyThemeMode(theme: ThemePreference): ThemeAppearance | null {...@@ -326,133 +328,6 @@ function legacyThemeMode(theme: ThemePreference): ThemeAppearance | null {
326 return theme === LEGACY_T3_CHAT_DARK_THEME_ID ? "dark" : null;328 return theme === LEGACY_T3_CHAT_DARK_THEME_ID ? "dark" : null;
327}329}
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
456/**331/**
457 * The standard T3 Code look as a theme palette, for seeding a new theme when332 * The standard T3 Code look as a theme palette, for seeding a new theme when
458 * no theme is installed. Distinct from {@link getDefaultThemeColors}, which333 * no theme is installed. Distinct from {@link getDefaultThemeColors}, which
packages/shared/src/themePalettes.ts+128-1
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1export const BUILT_IN_THEME_IDS = ["t3-chat", "grove", "ocean", "ember", "iris"] as const;1export 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. */
4export const MOBILE_DEFAULT_THEME_ID = "t3-code";4export const MOBILE_DEFAULT_THEME_ID = "t3-code";
55
6/**6/**
...@@ -121,6 +121,133 @@ export type ThemeDefinition = Readonly<{...@@ -121,6 +121,133 @@ export type ThemeDefinition = Readonly<{
121 managed?: boolean;121 managed?: boolean;
122}>;122}>;
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
124export const T3_CHAT_THEME: ThemeDefinition = {251export const T3_CHAT_THEME: ThemeDefinition = {
125 id: "t3-chat",252 id: "t3-chat",
126 label: "T3 Chat",253 label: "T3 Chat",