diff --git a/site/src/index.css b/site/src/index.css index 098396238f..56227c8770 100644 --- a/site/src/index.css +++ b/site/src/index.css @@ -199,9 +199,15 @@ success+error axis onto sky-blue (success) + vermilion/orange (destructive). Warning shifts to fuchsia so it does not collide with destructive states on the orange axis. - - dark-tritan / light-tritan: keep the red/green success+error - axis intact and move warning from amber to fuchsia because - amber and sky-blue blur together under tritanopia. + - dark-tritan / light-tritan: keep destructive states on the + red/orange axis and shift `success` (and the green-tinted + diff/git surfaces) onto sky-blue. This matches GitHub + Primer's tritanopia preset, where diff additions render in + blue rather than green so the success+destructive pair stays + consistent with the protan-deuter palette and with other + tritan-aware tools. Warning still moves from amber to + fuchsia because amber and sky-blue blur together under + tritanopia. */ .light-protan-deuter { --content-success: 199 89% 48%; @@ -252,24 +258,41 @@ --surface-git-deleted: 17 75% 15%; } .light-tritan { + --content-success: 199 89% 48%; --content-warning: 322 81% 43%; + --surface-green: 201 94% 86%; --surface-orange: 289 100% 98%; + --border-success: 199 89% 48%; + --border-green: 201 94% 86%; --border-warning: 322 81% 60%; + --highlight-green: 201 94% 36%; --highlight-orange: 322 81% 43%; --highlight-magenta: 322, 81%, 43%; --syntax-boolean: 322 81% 43%; + --syntax-number: 199 89% 38%; + --git-added: 199 89% 48%; --git-modified: 322 81% 43%; + --git-added-bright: 199 89% 48%; + --surface-git-added: 204 94% 94%; } .dark-tritan { + --content-success: 199 82% 67%; --content-warning: 322 81% 67%; + --surface-green: 201 80% 14%; --surface-orange: 322 70% 15%; --surface-magenta: 322 70% 15%; + --border-success: 199 82% 67%; + --border-green: 201 94% 86%; --border-magenta: 322 81% 72%; --border-warning: 322 81% 67%; + --highlight-green: 201 94% 86%; --highlight-orange: 322 81% 67%; --highlight-magenta: 322 81% 72%; --syntax-boolean: 322 81% 67%; + --git-added: 199 82% 67%; --git-modified: 322 81% 72%; + --git-added-bright: 199 89% 48%; + --surface-git-added: 201 80% 14%; } } diff --git a/site/src/theme/colorblind.test.ts b/site/src/theme/colorblind.test.ts index 99a03d4d05..c0fd15cdf4 100644 --- a/site/src/theme/colorblind.test.ts +++ b/site/src/theme/colorblind.test.ts @@ -2,6 +2,7 @@ import themes, { baseModeFor, CONCRETE_THEMES, isConcreteThemeName, + legacyAutoToSync, resolveThemeName, } from "."; @@ -71,8 +72,10 @@ describe("isConcreteThemeName", () => { } }); - it("rejects the auto preference (embeds require a concrete theme)", () => { + it("rejects legacy auto-family preferences", () => { expect(isConcreteThemeName("auto")).toBe(false); + expect(isConcreteThemeName("auto-protan-deuter")).toBe(false); + expect(isConcreteThemeName("auto-tritan")).toBe(false); }); it("rejects non-string and empty values", () => { @@ -84,6 +87,34 @@ describe("isConcreteThemeName", () => { }); }); +describe("legacyAutoToSync", () => { + it("maps each legacy auto value to its sync pair", () => { + expect(legacyAutoToSync("auto")).toEqual({ + mode: "sync", + light: "light", + dark: "dark", + }); + expect(legacyAutoToSync("auto-protan-deuter")).toEqual({ + mode: "sync", + light: "light-protan-deuter", + dark: "dark-protan-deuter", + }); + expect(legacyAutoToSync("auto-tritan")).toEqual({ + mode: "sync", + light: "light-tritan", + dark: "dark-tritan", + }); + }); + + it("returns null for concrete theme names and unrelated values", () => { + expect(legacyAutoToSync("dark")).toBeNull(); + expect(legacyAutoToSync("dark-tritan")).toBeNull(); + expect(legacyAutoToSync("")).toBeNull(); + expect(legacyAutoToSync(undefined)).toBeNull(); + expect(legacyAutoToSync("garbage")).toBeNull(); + }); +}); + describe("baseModeFor", () => { it("maps every concrete theme to its base mode", () => { for (const name of CONCRETE_THEMES) { @@ -120,4 +151,19 @@ describe("colorblind role palettes", () => { themes.dark.roles.danger, ); }); + + it("shifts tritan success off the base green role onto sky-blue", () => { + expect(themes["light-tritan"].roles.success).not.toEqual( + themes.light.roles.success, + ); + expect(themes["dark-tritan"].roles.success).not.toEqual( + themes.dark.roles.success, + ); + expect(themes["light-tritan"].roles.success).toEqual( + themes["light-protan-deuter"].roles.success, + ); + expect(themes["dark-tritan"].roles.success).toEqual( + themes["dark-protan-deuter"].roles.success, + ); + }); }); diff --git a/site/src/theme/darkTritan/roles.ts b/site/src/theme/darkTritan/roles.ts index 4d3492ce5d..44f42f6d8f 100644 --- a/site/src/theme/darkTritan/roles.ts +++ b/site/src/theme/darkTritan/roles.ts @@ -1,11 +1,6 @@ import type { Roles } from "../roles"; import colors from "../tailwindColors"; -// Tritanopia reduces blue/yellow discrimination, so the standard amber -// warning can blur into the sky-blue active/notice accents. Under -// tritanopia, red vs green remains intact, so we keep `success` on green, -// `error` on red, and `danger` on the base orange. Only `warning` shifts -// to a magenta/pink that stays distinct from blue and red states. const roles: Roles = { danger: { background: colors.orange[950], @@ -80,31 +75,31 @@ const roles: Roles = { }, }, success: { - background: colors.green[950], - outline: colors.green[500], - text: colors.green[50], + background: colors.sky[950], + outline: colors.sky[500], + text: colors.sky[50], fill: { - solid: colors.green[600], - outline: colors.green[600], + solid: colors.sky[600], + outline: colors.sky[600], text: colors.white, }, disabled: { - background: colors.green[950], - outline: colors.green[800], - text: colors.green[200], + background: colors.sky[950], + outline: colors.sky[800], + text: colors.sky[200], fill: { - solid: colors.green[800], - outline: colors.green[800], + solid: colors.sky[800], + outline: colors.sky[800], text: colors.white, }, }, hover: { - background: colors.green[900], - outline: colors.green[500], + background: colors.sky[900], + outline: colors.sky[500], text: colors.white, fill: { - solid: colors.green[500], - outline: colors.green[500], + solid: colors.sky[500], + outline: colors.sky[500], text: colors.white, }, }, diff --git a/site/src/theme/index.ts b/site/src/theme/index.ts index 80d1a4df02..33f140cdaa 100644 --- a/site/src/theme/index.ts +++ b/site/src/theme/index.ts @@ -53,6 +53,35 @@ export const isConcreteThemeName = ( return typeof value === "string" && concreteThemeSet.has(value); }; +type LegacyAutoSync = { + mode: "sync"; + light: ConcreteThemeName; + dark: ConcreteThemeName; +}; + +const LEGACY_AUTO_SYNC: Record = { + auto: { mode: "sync", light: "light", dark: "dark" }, + "auto-protan-deuter": { + mode: "sync", + light: "light-protan-deuter", + dark: "dark-protan-deuter", + }, + "auto-tritan": { + mode: "sync", + light: "light-tritan", + dark: "dark-tritan", + }, +}; + +export const legacyAutoToSync = ( + preference: string | undefined, +): LegacyAutoSync | null => { + if (!preference) { + return null; + } + return LEGACY_AUTO_SYNC[preference] ?? null; +}; + export const resolveThemeName = ( preference: string | undefined, osScheme: "dark" | "light", diff --git a/site/src/theme/lightTritan/roles.ts b/site/src/theme/lightTritan/roles.ts index 49efb72950..2d55e73b18 100644 --- a/site/src/theme/lightTritan/roles.ts +++ b/site/src/theme/lightTritan/roles.ts @@ -1,10 +1,6 @@ import type { Roles } from "../roles"; import colors from "../tailwindColors"; -// Tritanopia reduces blue/yellow discrimination. Red vs green remains -// intact, so we keep `success` on green, `error` on red, and `danger` -// on the base orange. Only `warning` shifts to a magenta/fuchsia that -// stays distinct from the blue accents and red destructive states. const roles: Roles = { danger: { background: colors.orange[50], @@ -79,31 +75,31 @@ const roles: Roles = { }, }, success: { - background: colors.green[50], - outline: colors.green[500], - text: colors.green[950], + background: colors.sky[100], + outline: colors.sky[500], + text: colors.sky[950], fill: { - solid: colors.green[600], - outline: colors.green[600], + solid: colors.sky[600], + outline: colors.sky[600], text: colors.white, }, disabled: { - background: colors.green[50], - outline: colors.green[800], - text: colors.green[800], + background: colors.sky[50], + outline: colors.sky[800], + text: colors.sky[800], fill: { - solid: colors.green[800], - outline: colors.green[800], + solid: colors.sky[800], + outline: colors.sky[800], text: colors.white, }, }, hover: { - background: colors.green[100], - outline: colors.green[500], + background: colors.sky[200], + outline: colors.sky[500], text: colors.black, fill: { - solid: colors.green[500], - outline: colors.green[500], + solid: colors.sky[500], + outline: colors.sky[500], text: colors.white, }, },