| author | |
| committer | |
| log | 0a7c662d39329eeb3cffe00d66a31f1a8241b3d7 |
| tree | 5c7080cb67e84ec872983324d950140f7a286d52 |
| parent | b91a000a17ddb4937397151a0a39a07990ad6473 |
| signature | Signed by PGP key B5690EEEBB952194 |
8 files changed, 51 insertions(+), 36 deletions(-)
apps/web/index.html+2-2| ... | ... | @@ -37,7 +37,7 @@ |
| 37 | 37 | light: { |
| 38 | 38 | background: "#fdf7fd", |
| 39 | 39 | foreground: "#501854", |
| 40 | accent: "#e33f86", | |
| 40 | accent: "#db2777", | |
| 41 | 41 | chrome: "#fdf7fd", |
| 42 | 42 | }, |
| 43 | 43 | dark: { |
| ... | ... | @@ -54,7 +54,7 @@ |
| 54 | 54 | light: { |
| 55 | 55 | background: "#fdf7fd", |
| 56 | 56 | foreground: "#501854", |
| 57 | accent: "#e33f86", | |
| 57 | accent: "#db2777", | |
| 58 | 58 | chrome: "#fdf7fd", |
| 59 | 59 | }, |
| 60 | 60 | dark: { |
apps/web/src/components/chat/ModelPickerSidebar.tsx+1-1| ... | ... | @@ -29,7 +29,7 @@ const SELECTED_INDICATOR_CLASS = |
| 29 | 29 | "pointer-events-none absolute -right-1 top-1/2 z-10 h-5 w-0.75 -translate-y-1/2 rounded-l-full bg-primary"; |
| 30 | 30 | const BADGE_BASE_CLASS = |
| 31 | 31 | "pointer-events-none absolute -right-0.5 top-0.5 z-10 flex size-3.5 items-center justify-center rounded-full bg-transparent shadow-sm "; |
| 32 | const NEW_BADGE_CLASS = `${BADGE_BASE_CLASS} text-update `; | |
| 32 | const NEW_BADGE_CLASS = `${BADGE_BASE_CLASS} text-update-foreground `; | |
| 33 | 33 | |
| 34 | 34 | /** Opens toward the rail so the list stays readable (not over the model names). */ |
| 35 | 35 | const PICKER_TOOLTIP_SIDE = "left" as const; |
apps/web/src/components/settings/ProviderInstanceCard.tsx+1-1| ... | ... | @@ -619,7 +619,7 @@ export function ProviderInstanceCard({ |
| 619 | 619 | "size-5 rounded-sm p-0", |
| 620 | 620 | versionAdvisory.emphasis === "strong" |
| 621 | 621 | ? "text-warning hover:text-warning" |
| 622 | : "text-update hover:text-update", | |
| 622 | : "text-update-foreground hover:text-update-foreground", | |
| 623 | 623 | )} |
| 624 | 624 | aria-label="Update available — view details" |
| 625 | 625 | > |
apps/web/src/components/settings/themeInspector.test.ts+3| ... | ... | @@ -31,6 +31,9 @@ describe("changedThemePaintKinds", () => { |
| 31 | 31 | describe("themeRoleFromUtilityClass", () => { |
| 32 | 32 | it("maps semantic utilities to their actual palette role", () => { |
| 33 | 33 | expect(themeRoleFromUtilityClass("text-foreground", "foreground")).toBe("text"); |
| 34 | expect(themeRoleFromUtilityClass("text-muted-foreground", "foreground")).toBe( | |
| 35 | "mutedForeground", | |
| 36 | ); | |
| 34 | 37 | expect(themeRoleFromUtilityClass("bg-primary/90", "background")).toBe("messageAction"); |
| 35 | 38 | expect(themeRoleFromUtilityClass("ring-ring", "border")).toBe("focus"); |
| 36 | 39 | }); |
apps/web/src/components/settings/themeInspector.ts+1-1| ... | ... | @@ -38,7 +38,7 @@ const THEME_UTILITY_ROLES: Readonly<Partial<Record<string, ThemeColorRole>>> = { |
| 38 | 38 | secondary: "secondary", |
| 39 | 39 | "secondary-foreground": "secondaryForeground", |
| 40 | 40 | muted: "muted", |
| 41 | "muted-foreground": "textMuted", | |
| 41 | "muted-foreground": "mutedForeground", | |
| 42 | 42 | placeholder: "placeholder", |
| 43 | 43 | "secondary-label": "secondaryLabel", |
| 44 | 44 | "icon-muted": "iconMuted", |
apps/web/src/index.css+5-5| ... | ... | @@ -932,7 +932,7 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 932 | 932 | --destructive: var(--error); |
| 933 | 933 | --border: var(--color-zinc-200); |
| 934 | 934 | --input: var(--color-zinc-300); |
| 935 | --ring: oklch(0.488 0.217 264); | |
| 935 | --ring: var(--primary); | |
| 936 | 936 | --destructive-foreground: var(--error-foreground); |
| 937 | 937 | --info: var(--color-blue-500); |
| 938 | 938 | --info-foreground: var(--color-blue-700); |
| ... | ... | @@ -976,7 +976,7 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 976 | 976 | --card-foreground: var(--color-neutral-100); |
| 977 | 977 | --popover: color-mix(in srgb, var(--background) 94%, var(--color-white)); |
| 978 | 978 | --popover-foreground: var(--color-neutral-100); |
| 979 | --primary: oklch(0.588 0.217 264); | |
| 979 | --primary: oklch(0.571 0.21 264); | |
| 980 | 980 | --primary-foreground: var(--color-white); |
| 981 | 981 | --secondary: --alpha(var(--color-white) / 4%); |
| 982 | 982 | --secondary-foreground: var(--color-neutral-100); |
| ... | ... | @@ -998,7 +998,7 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil |
| 998 | 998 | --destructive: var(--error); |
| 999 | 999 | --border: --alpha(var(--color-white) / 6%); |
| 1000 | 1000 | --input: --alpha(var(--color-white) / 8%); |
| 1001 | --ring: oklch(0.588 0.217 264); | |
| 1001 | --ring: var(--primary); | |
| 1002 | 1002 | --destructive-foreground: var(--error-foreground); |
| 1003 | 1003 | --info: var(--color-blue-500); |
| 1004 | 1004 | --info-foreground: var(--color-blue-400); |
| ... | ... | @@ -1109,7 +1109,7 @@ html.dark[data-theme-id] { |
| 1109 | 1109 | --secondary: var(--app-theme-secondary); |
| 1110 | 1110 | --secondary-foreground: var(--app-theme-secondary-foreground); |
| 1111 | 1111 | --muted: var(--app-theme-muted); |
| 1112 | --muted-foreground: var(--app-theme-text-muted); | |
| 1112 | --muted-foreground: var(--app-theme-muted-foreground); | |
| 1113 | 1113 | --placeholder: var(--app-theme-placeholder); |
| 1114 | 1114 | --secondary-label: var(--app-theme-secondary-label); |
| 1115 | 1115 | --icon-muted: var(--app-theme-icon-muted); |
| ... | ... | @@ -1359,7 +1359,7 @@ html[data-theme-id] [data-app-sidebar] { |
| 1359 | 1359 | --accent: var(--app-theme-accent-surface); |
| 1360 | 1360 | --accent-foreground: var(--app-theme-accent-surface-foreground); |
| 1361 | 1361 | --muted: var(--app-theme-muted); |
| 1362 | --muted-foreground: var(--app-theme-text-muted); | |
| 1362 | --muted-foreground: var(--app-theme-muted-foreground); | |
| 1363 | 1363 | --border: var(--app-theme-border); |
| 1364 | 1364 | --input: var(--app-theme-input); |
| 1365 | 1365 | --sidebar: var(--app-theme-sidebar); |
apps/web/src/themePalette.test.ts+13-8| ... | ... | @@ -113,6 +113,8 @@ describe("theme files", () => { |
| 113 | 113 | expect(contrastRatio(colors.text, colors.canvas)).toBeGreaterThanOrEqual(7); |
| 114 | 114 | expect(contrastRatio(colors.textMuted, colors.canvas)).toBeGreaterThanOrEqual(4.5); |
| 115 | 115 | expect(contrastRatio(colors.textMuted, colors.canvas)).toBeLessThan(5.5); |
| 116 | expect(contrastRatio(colors.mutedForeground, colors.muted)).toBeGreaterThanOrEqual(4.5); | |
| 117 | expect(contrastRatio(colors.placeholder, colors.surfaceRaised)).toBeGreaterThanOrEqual(4.5); | |
| 116 | 118 | expect(colors.secondaryLabel).toBe(colors.textMuted); |
| 117 | 119 | expect(contrastRatio(colors.accentForeground, colors.accent)).toBeGreaterThanOrEqual(4.5); |
| 118 | 120 | expect( |
| ... | ... | @@ -169,7 +171,7 @@ describe("theme files", () => { |
| 169 | 171 | colors: { |
| 170 | 172 | canvas: "#07152f", |
| 171 | 173 | accent: "#67c2ff", |
| 172 | placeholder: "#8f8699", | |
| 174 | placeholder: "#968d9f", | |
| 173 | 175 | }, |
| 174 | 176 | }); |
| 175 | 177 | }); |
| ... | ... | @@ -315,9 +317,7 @@ describe("theme files", () => { |
| 315 | 317 | 4.5, |
| 316 | 318 | ); |
| 317 | 319 | expect(contrastRatio(colors.sidebarForeground, colors.sidebar)).toBeGreaterThanOrEqual(4.5); |
| 318 | // The live light primary is intended for filled controls and clears the | |
| 319 | // non-text UI-component threshold rather than normal-text AA. | |
| 320 | expect(contrastRatio(colors.accentForeground, colors.accent)).toBeGreaterThanOrEqual(3); | |
| 320 | expect(contrastRatio(colors.accentForeground, colors.accent)).toBeGreaterThanOrEqual(4.5); | |
| 321 | 321 | } |
| 322 | 322 | }); |
| 323 | 323 | |
| ... | ... | @@ -340,9 +340,7 @@ describe("theme files", () => { |
| 340 | 340 | 1, |
| 341 | 341 | ); |
| 342 | 342 | } |
| 343 | expect(contrastRatio(colors!.accentForeground, colors!.accent)).toBeGreaterThanOrEqual( | |
| 344 | theme === T3_CHAT_THEME ? 3 : 4.5, | |
| 345 | ); | |
| 343 | expect(contrastRatio(colors!.accentForeground, colors!.accent)).toBeGreaterThanOrEqual(4.5); | |
| 346 | 344 | expect( |
| 347 | 345 | contrastRatio(colors!.toolbarControlForeground, colors!.toolbarControl), |
| 348 | 346 | ).toBeGreaterThanOrEqual(4.5); |
| ... | ... | @@ -351,7 +349,14 @@ describe("theme files", () => { |
| 351 | 349 | ).toBeGreaterThanOrEqual(4.5); |
| 352 | 350 | expect( |
| 353 | 351 | contrastRatio(colors!.messageActionForeground, colors!.messageAction), |
| 354 | ).toBeGreaterThanOrEqual(theme === T3_CHAT_THEME ? 3 : 4.5); | |
| 352 | ).toBeGreaterThanOrEqual(4.5); | |
| 353 | expect( | |
| 354 | contrastRatio(colors!.messageActionForeground, colors!.messageActionHover), | |
| 355 | ).toBeGreaterThanOrEqual(4.5); | |
| 356 | expect(contrastRatio(colors!.mutedForeground, colors!.muted)).toBeGreaterThanOrEqual(4.5); | |
| 357 | expect(contrastRatio(colors!.placeholder, colors!.surfaceRaised)).toBeGreaterThanOrEqual( | |
| 358 | 4.5, | |
| 359 | ); | |
| 355 | 360 | } |
| 356 | 361 | } |
| 357 | 362 | }); |
apps/web/src/themePalette.ts+25-18| ... | ... | @@ -325,6 +325,7 @@ function legacyThemeMode(theme: ThemePreference): ThemeAppearance | null { |
| 325 | 325 | // Measured from the live t3.chat default theme. Translucent chat surfaces are |
| 326 | 326 | // flattened over --chat-background so this opaque palette reproduces the |
| 327 | 327 | // pixels users see after T3 Chat's blur and noise layers are composited. |
| 328 | // Foreground pairs deviate where necessary to keep normal text at WCAG AA. | |
| 328 | 329 | const T3_CHAT_LIGHT_COLORS: ThemeColors = { |
| 329 | 330 | canvas: "#fdf7fd", |
| 330 | 331 | // T3 Code's workspace header belongs to the chat panel, so keep it seamless |
| ... | ... | @@ -346,31 +347,31 @@ const T3_CHAT_LIGHT_COLORS: ThemeColors = { |
| 346 | 347 | border: "#eee1ed", |
| 347 | 348 | input: "#e7c1dc", |
| 348 | 349 | focus: "#db2777", |
| 349 | accent: "#e33f86", | |
| 350 | accent: "#db2777", | |
| 350 | 351 | accentForeground: "#ffffff", |
| 351 | 352 | secondary: "#f1c4e6", |
| 352 | 353 | secondaryForeground: "#77347c", |
| 353 | 354 | muted: "#eaa7cb", |
| 354 | mutedForeground: "#ac1668", | |
| 355 | placeholder: "#ad83b0", | |
| 355 | mutedForeground: "#8d1255", | |
| 356 | placeholder: "#8b5f90", | |
| 356 | 357 | secondaryLabel: "#ac1668", |
| 357 | 358 | iconMuted: "#ac1668", |
| 358 | 359 | error: "#f7086c", |
| 359 | 360 | errorForeground: "#9d174d", |
| 360 | 361 | errorSurface: "#fde4f1", |
| 361 | 362 | warning: "#f59e0b", |
| 362 | warningForeground: "#b45309", | |
| 363 | warningForeground: "#b05109", | |
| 363 | 364 | warningSurface: "#fcf0ea", |
| 364 | update: "#e33f86", | |
| 365 | update: "#db2777", | |
| 365 | 366 | updateForeground: "#ac1668", |
| 366 | 367 | updateSurface: "#fadfef", |
| 367 | 368 | accentSurface: "#f3e6f5", |
| 368 | 369 | accentSurfaceForeground: "#454554", |
| 369 | 370 | messageSurface: "#f7def2", |
| 370 | 371 | messageForeground: "#492c61", |
| 371 | messageAction: "#e33f86", | |
| 372 | messageAction: "#db2777", | |
| 372 | 373 | messageActionForeground: "#ffffff", |
| 373 | messageActionHover: "#d56698", | |
| 374 | messageActionHover: "#c12269", | |
| 374 | 375 | // T3 Chat uses a light lavender code surface in light mode. Keeping the |
| 375 | 376 | // dark plum pair here also leaked the dark palette into T3 Code's diffs. |
| 376 | 377 | codeBackground: "#f5ecf9", |
| ... | ... | @@ -422,7 +423,7 @@ const T3_CHAT_DARK_COLORS: ThemeColors = { |
| 422 | 423 | secondaryForeground: "#d4c7e1", |
| 423 | 424 | muted: "#423a45", |
| 424 | 425 | mutedForeground: "#e7d0dd", |
| 425 | placeholder: "#8f8699", | |
| 426 | placeholder: "#968d9f", | |
| 426 | 427 | secondaryLabel: "#e7d0dd", |
| 427 | 428 | iconMuted: "#d4c7e1", |
| 428 | 429 | error: "#9d174d", |
| ... | ... | @@ -549,8 +550,8 @@ const T3_CODE_DARK_THEME_COLORS: ThemeColors = { |
| 549 | 550 | textMuted: "#818181", |
| 550 | 551 | border: "#191919", |
| 551 | 552 | input: "#1e1e1e", |
| 552 | focus: "#366ffb", | |
| 553 | accent: "#366ffb", | |
| 553 | focus: "#346bf1", | |
| 554 | accent: "#346bf1", | |
| 554 | 555 | accentForeground: "#ffffff", |
| 555 | 556 | secondary: "#141414", |
| 556 | 557 | secondaryForeground: "#f5f5f5", |
| ... | ... | @@ -565,16 +566,16 @@ const T3_CODE_DARK_THEME_COLORS: ThemeColors = { |
| 565 | 566 | warning: "#fe9a00", |
| 566 | 567 | warningForeground: "#ffb900", |
| 567 | 568 | warningSurface: "#312108", |
| 568 | update: "#366ffb", | |
| 569 | update: "#346bf1", | |
| 569 | 570 | updateForeground: "#51a2ff", |
| 570 | updateSurface: "#121c35", | |
| 571 | updateSurface: "#121b34", | |
| 571 | 572 | accentSurface: "#141414", |
| 572 | 573 | accentSurfaceForeground: "#f5f5f5", |
| 573 | 574 | messageSurface: "#141414", |
| 574 | 575 | messageForeground: "#f5f5f5", |
| 575 | messageAction: "#366ffb", | |
| 576 | messageAction: "#346bf1", | |
| 576 | 577 | messageActionForeground: "#ffffff", |
| 577 | messageActionHover: "#3265e3", | |
| 578 | messageActionHover: "#3061d9", | |
| 578 | 579 | codeBackground: "#111111", |
| 579 | 580 | codeForeground: "#f5f5f5", |
| 580 | 581 | sidebar: "#000000", |
| ... | ... | @@ -941,12 +942,14 @@ export function createVividThemeColors( |
| 941 | 942 | const sidebarRgb = themeOklchToRgb(sidebar); |
| 942 | 943 | const surface = surfaceAt(0.015); |
| 943 | 944 | const surfaceRaised = surfaceAt(0.05); |
| 945 | const surfaceRaisedRgb = themeOklchToRgb(surfaceRaised); | |
| 944 | 946 | const surfaceOverlay = surfaceAt(0.075); |
| 945 | 947 | const border = surfaceAt(dark ? 0.16 : 0.12, Math.min(0.07, accent.C * 0.35)); |
| 946 | 948 | const input = surfaceAt(dark ? 0.21 : 0.16, Math.min(0.08, accent.C * 0.4)); |
| 947 | 949 | const secondary = surfaceAt(dark ? 0.1 : 0.06, Math.min(0.09, accent.C * 0.5)); |
| 948 | 950 | const secondaryRgb = themeOklchToRgb(secondary); |
| 949 | 951 | const muted = surfaceAt(dark ? 0.06 : 0.04, Math.min(0.06, accent.C * 0.35)); |
| 952 | const mutedRgb = themeOklchToRgb(muted); | |
| 950 | 953 | const accentSurface = surfaceAt(dark ? 0.13 : 0.08, Math.min(0.11, accent.C * 0.55)); |
| 951 | 954 | const accentSurfaceRgb = themeOklchToRgb(accentSurface); |
| 952 | 955 | const messageSurface = surfaceAt(dark ? 0.16 : 0.1, Math.min(0.13, accent.C * 0.6)); |
| ... | ... | @@ -958,6 +961,8 @@ export function createVividThemeColors( |
| 958 | 961 | themeRgbToHexColor( |
| 959 | 962 | themeOklchToRgb(solveOklchLightness(textBase, surfaceRgb, 4.6, dark ? "lighter" : "darker")), |
| 960 | 963 | ); |
| 964 | const mutedForeground = foregroundOn(mutedRgb); | |
| 965 | const placeholder = foregroundOn(surfaceRaisedRgb); | |
| 961 | 966 | |
| 962 | 967 | const actionHover: ThemeOklch = { ...action, L: action.L + (dark ? 0.06 : -0.06) }; |
| 963 | 968 | |
| ... | ... | @@ -986,8 +991,8 @@ export function createVividThemeColors( |
| 986 | 991 | secondary: hex(secondary), |
| 987 | 992 | secondaryForeground: foregroundOn(secondaryRgb), |
| 988 | 993 | muted: hex(muted), |
| 989 | mutedForeground: themeRgbToHexColor(textMutedRgb), | |
| 990 | placeholder: themeRgbToHexColor(textMutedRgb), | |
| 994 | mutedForeground, | |
| 995 | placeholder, | |
| 991 | 996 | secondaryLabel: themeRgbToHexColor(textMutedRgb), |
| 992 | 997 | iconMuted: themeRgbToHexColor(textMutedRgb), |
| 993 | 998 | update: themeRgbToHexColor(accentRgb), |
| ... | ... | @@ -1174,6 +1179,8 @@ export function createManagedThemeColors( |
| 1174 | 1179 | const surfaceOverlay = mixThemeRgbColors(canvas, text, appearance === "dark" ? 0.18 : 0.06); |
| 1175 | 1180 | const secondary = mixThemeRgbColors(canvas, accent, appearance === "dark" ? 0.2 : 0.08); |
| 1176 | 1181 | const muted = mixThemeRgbColors(canvas, accent, appearance === "dark" ? 0.13 : 0.06); |
| 1182 | const mutedForeground = readableThemeText(muted, text, 1, 4.6); | |
| 1183 | const placeholder = readableThemeText(surfaceRaised, text, 1, 4.6); | |
| 1177 | 1184 | const accentSurface = mixThemeRgbColors(canvas, accent, appearance === "dark" ? 0.3 : 0.14); |
| 1178 | 1185 | const messageSurface = mixThemeRgbColors(canvas, accent, appearance === "dark" ? 0.36 : 0.18); |
| 1179 | 1186 | const toolbarControl = mixThemeRgbColors(chrome, accent, appearance === "dark" ? 0.2 : 0.08); |
| ... | ... | @@ -1244,8 +1251,8 @@ export function createManagedThemeColors( |
| 1244 | 1251 | secondary: themeRgbToHexColor(secondary), |
| 1245 | 1252 | secondaryForeground: themeRgbToHexColor(readableThemeForeground(secondary)), |
| 1246 | 1253 | muted: themeRgbToHexColor(muted), |
| 1247 | mutedForeground: themeRgbToHexColor(textMuted), | |
| 1248 | placeholder: themeRgbToHexColor(textMuted), | |
| 1254 | mutedForeground: themeRgbToHexColor(mutedForeground), | |
| 1255 | placeholder: themeRgbToHexColor(placeholder), | |
| 1249 | 1256 | secondaryLabel: themeRgbToHexColor(textMuted), |
| 1250 | 1257 | iconMuted: themeRgbToHexColor(textMuted), |
| 1251 | 1258 | accentSurface: themeRgbToHexColor(accentSurface), |