feat(app): color themes (#30824)

Co-authored-by: LukeParkerDev <10430890+Hona@users.noreply.github.com>
This commit is contained in:
Aarav Sareen 2026-06-05 06:23:36 +05:30 committed by GitHub
commit 605ae48c6f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 1040 additions and 87 deletions

View file

@ -1,9 +1,12 @@
// @refresh reload
import { createEffect, onMount } from "solid-js"
import { createStore } from "solid-js/store"
import { makeEventListener } from "@solid-primitives/event-listener"
import { createSimpleContext } from "../context/helper"
import oc2ThemeJson from "./themes/oc-2.json"
import { resolveThemeVariant, themeToCss } from "./resolve"
import { resolveThemeVariantV2, themeV2ToCss } from "./v2/resolve"
import type { DesktopTheme } from "./types"
export type ColorScheme = "light" | "dark" | "system"
@ -132,15 +135,17 @@ function applyThemeCss(theme: DesktopTheme, themeId: string, mode: "light" | "da
const variant = isDark ? theme.dark : theme.light
const tokens = resolveThemeVariant(variant, isDark)
const css = themeToCss(tokens)
const v2 = themeV2ToCss(resolveThemeVariantV2(variant, isDark))
if (themeId !== "oc-2") {
write(isDark ? STORAGE_KEYS.THEME_CSS_DARK : STORAGE_KEYS.THEME_CSS_LIGHT, css)
write(isDark ? STORAGE_KEYS.THEME_CSS_DARK : STORAGE_KEYS.THEME_CSS_LIGHT, `${css}\n ${v2}`)
}
const fullCss = `:root {
color-scheme: ${mode};
--text-mix-blend-mode: ${isDark ? "plus-lighter" : "multiply"};
${css}
${v2}
}`
document.getElementById("oc-theme-preload")?.remove()
@ -160,7 +165,8 @@ function cacheThemeVariants(theme: DesktopTheme, themeId: string) {
const variant = isDark ? theme.dark : theme.light
const tokens = resolveThemeVariant(variant, isDark)
const css = themeToCss(tokens)
write(isDark ? STORAGE_KEYS.THEME_CSS_DARK : STORAGE_KEYS.THEME_CSS_LIGHT, css)
const v2 = themeV2ToCss(resolveThemeVariantV2(variant, isDark))
write(isDark ? STORAGE_KEYS.THEME_CSS_DARK : STORAGE_KEYS.THEME_CSS_LIGHT, `${css}\n ${v2}`)
}
}

View file

@ -154,6 +154,13 @@
"additionalProperties": {
"$ref": "#/definitions/ColorValue"
}
},
"v2Overrides": {
"type": "object",
"description": "Optional direct overrides for any v2 CSS variable (without -- prefix)",
"additionalProperties": {
"type": "string"
}
}
}
}

View file

@ -8,6 +8,8 @@ export type {
ResolvedTheme,
ColorValue,
CssVarRef,
V2ColorValue,
ResolvedV2Theme,
} from "./types"
export {
@ -30,6 +32,7 @@ export {
} from "./color"
export { resolveThemeVariant, resolveTheme, themeToCss } from "./resolve"
export { resolveThemeVariantV2, resolveThemeV2, themeV2ToCss, generateV2Primitives } from "./v2/resolve"
export { applyTheme, loadThemeFromUrl, getActiveTheme, removeTheme, setColorScheme } from "./loader"
export { ThemeProvider, useTheme, type ColorScheme } from "./context"

View file

@ -1,5 +1,6 @@
import type { DesktopTheme, ResolvedTheme } from "./types"
import type { DesktopTheme, ResolvedTheme, ResolvedV2Theme } from "./types"
import { resolveThemeVariant, themeToCss } from "./resolve"
import { resolveThemeVariantV2, themeV2ToCss } from "./v2/resolve"
let activeTheme: DesktopTheme | null = null
const THEME_STYLE_ID = "opencode-theme"
@ -19,17 +20,25 @@ export function applyTheme(theme: DesktopTheme, themeId?: string): void {
activeTheme = theme
const lightTokens = resolveThemeVariant(theme.light, false)
const darkTokens = resolveThemeVariant(theme.dark, true)
const lightV2Tokens = resolveThemeVariantV2(theme.light, false)
const darkV2Tokens = resolveThemeVariantV2(theme.dark, true)
const targetThemeId = themeId ?? theme.id
const css = buildThemeCss(lightTokens, darkTokens, targetThemeId)
const css = buildThemeCss(lightTokens, darkTokens, lightV2Tokens, darkV2Tokens, targetThemeId)
const themeStyleElement = ensureLoaderStyleElement()
themeStyleElement.textContent = css
document.documentElement.setAttribute("data-theme", targetThemeId)
}
function buildThemeCss(light: ResolvedTheme, dark: ResolvedTheme, themeId: string): string {
function buildThemeCss(
light: ResolvedTheme,
dark: ResolvedTheme,
lightV2: ResolvedV2Theme,
darkV2: ResolvedV2Theme,
themeId: string,
): string {
const isDefaultTheme = themeId === "oc-2"
const lightCss = themeToCss(light)
const darkCss = themeToCss(dark)
const lightCss = `${themeToCss(light)}\n ${themeV2ToCss(lightV2)}`
const darkCss = `${themeToCss(dark)}\n ${themeV2ToCss(darkV2)}`
if (isDefaultTheme) {
return `

View file

@ -43,6 +43,17 @@
"markdown-image": "#30b3ff",
"markdown-image-text": "#24f6d9",
"markdown-code-block": "#203022"
},
"v2Overrides": {
"v2-text-text-base": "#353535",
"v2-text-text-muted": "#748476",
"v2-text-text-faint": "#748476",
"v2-background-bg-accent": "var(--v2-green-600)",
"v2-text-text-accent": "var(--v2-green-600)",
"v2-text-text-accent-hover": "var(--v2-green-700)",
"v2-icon-icon-accent": "var(--v2-green-600)",
"v2-icon-icon-accent-hover": "var(--v2-green-700)",
"v2-border-border-focus": "var(--v2-green-500)"
}
},
"dark": {
@ -86,6 +97,17 @@
"markdown-image": "#30b3ff",
"markdown-image-text": "#24f6d9",
"markdown-code-block": "#62ff94"
},
"v2Overrides": {
"v2-text-text-base": "#ececec",
"v2-text-text-muted": "#8ca391",
"v2-text-text-faint": "#8ca391",
"v2-background-bg-accent": "var(--v2-green-600)",
"v2-text-text-accent": "var(--v2-green-400)",
"v2-text-text-accent-hover": "var(--v2-green-300)",
"v2-icon-icon-accent": "var(--v2-green-400)",
"v2-icon-icon-accent-hover": "var(--v2-green-300)",
"v2-border-border-focus": "var(--v2-green-500)"
}
}
}

View file

@ -42,6 +42,194 @@
"syntax-diff-delete": "#ff8c00",
"syntax-diff-unknown": "#a753ae",
"surface-critical-base": "#FFF2F0"
},
"v2Overrides": {
"v2-grey-100": "#ffffffff",
"v2-grey-200": "#fafafaff",
"v2-grey-300": "#eeeeeeff",
"v2-grey-400": "#d4d4d4ff",
"v2-grey-500": "#aeaeaeff",
"v2-grey-600": "#808080ff",
"v2-grey-700": "#5c5c5cff",
"v2-grey-800": "#3a3a3aff",
"v2-grey-900": "#242424ff",
"v2-grey-1000": "#161616ff",
"v2-grey-1100": "#080808ff",
"v2-grey-1200": "#000000ff",
"v2-red-100": "#fcecebff",
"v2-red-200": "#f6d5d3ff",
"v2-red-300": "#f2bbb7ff",
"v2-red-400": "#f29b96ff",
"v2-red-500": "#f17471ff",
"v2-red-600": "#f1484fff",
"v2-red-700": "#d92e3cff",
"v2-red-800": "#b82d35ff",
"v2-red-900": "#97252bff",
"v2-red-1000": "#7a1f23ff",
"v2-red-1100": "#5f1a1cff",
"v2-red-1200": "#461516ff",
"v2-orange-100": "#fdf2edff",
"v2-orange-200": "#ffe7dcff",
"v2-orange-300": "#ffd8c6ff",
"v2-orange-400": "#ffc1a4ff",
"v2-orange-500": "#ffa478ff",
"v2-orange-600": "#ff8648ff",
"v2-orange-700": "#ee7330ff",
"v2-orange-800": "#d16427ff",
"v2-orange-900": "#b35624ff",
"v2-orange-1000": "#954c27ff",
"v2-orange-1100": "#723d22ff",
"v2-orange-1200": "#5a2c14ff",
"v2-yellow-100": "#fefaecff",
"v2-yellow-200": "#fcefd0ff",
"v2-yellow-300": "#f7e5b5ff",
"v2-yellow-400": "#f3da9bff",
"v2-yellow-500": "#f2cf76ff",
"v2-yellow-600": "#f6c251ff",
"v2-yellow-700": "#e7af36ff",
"v2-yellow-800": "#cb9f34ff",
"v2-yellow-900": "#ac8833ff",
"v2-yellow-1000": "#8e7231ff",
"v2-yellow-1100": "#68552bff",
"v2-yellow-1200": "#4b4025ff",
"v2-green-100": "#e7f9eaff",
"v2-green-200": "#d0f0d5ff",
"v2-green-300": "#b8e9c1ff",
"v2-green-400": "#96e3a6ff",
"v2-green-500": "#6bd586ff",
"v2-green-600": "#49c970ff",
"v2-green-700": "#2eaf5aff",
"v2-green-800": "#198b43ff",
"v2-green-900": "#1d783cff",
"v2-green-1000": "#196130ff",
"v2-green-1100": "#164c26ff",
"v2-green-1200": "#14361dff",
"v2-cyan-100": "#e2f7fbff",
"v2-cyan-200": "#c4edf4ff",
"v2-cyan-300": "#a3e4efff",
"v2-cyan-400": "#65d9ebff",
"v2-cyan-500": "#00c5dfff",
"v2-cyan-600": "#00abcfff",
"v2-cyan-700": "#0096b8ff",
"v2-cyan-800": "#007d9bff",
"v2-cyan-900": "#006c85ff",
"v2-cyan-1000": "#005a6eff",
"v2-cyan-1100": "#004756ff",
"v2-cyan-1200": "#00353fff",
"v2-blue-100": "#ecf1feff",
"v2-blue-200": "#d7e2fcff",
"v2-blue-300": "#c3d4fdff",
"v2-blue-400": "#a2bcffff",
"v2-blue-500": "#7698fdff",
"v2-blue-600": "#3b5cf6ff",
"v2-blue-700": "#3250dfff",
"v2-blue-800": "#2c47c8ff",
"v2-blue-900": "#263fa9ff",
"v2-blue-1000": "#22388fff",
"v2-blue-1100": "#1c2e70ff",
"v2-blue-1200": "#1b2852ff",
"v2-purple-100": "#ebecfeff",
"v2-purple-200": "#d5d5fcff",
"v2-purple-300": "#b9b8f5ff",
"v2-purple-400": "#9e99f7ff",
"v2-purple-500": "#8271f8ff",
"v2-purple-600": "#7152f4ff",
"v2-purple-700": "#623be2ff",
"v2-purple-800": "#5230c2ff",
"v2-purple-900": "#442aa1ff",
"v2-purple-1000": "#361f83ff",
"v2-purple-1100": "#2b1b6aff",
"v2-purple-1200": "#221358ff",
"v2-pink-100": "#fdecf3ff",
"v2-pink-200": "#f7d5e4ff",
"v2-pink-300": "#fabcd8ff",
"v2-pink-400": "#f799c6ff",
"v2-pink-500": "#f26cb2ff",
"v2-pink-600": "#f64aabff",
"v2-pink-700": "#e4429eff",
"v2-pink-800": "#c83d8bff",
"v2-pink-900": "#aa3576ff",
"v2-pink-1000": "#8c2d61ff",
"v2-pink-1100": "#6f284fff",
"v2-pink-1200": "#5c1d3fff",
"v2-background-bg-base": "var(--v2-grey-100)",
"v2-background-bg-deep": "var(--v2-grey-200)",
"v2-background-bg-layer-01": "var(--v2-grey-200)",
"v2-background-bg-layer-02": "var(--v2-grey-300)",
"v2-background-bg-layer-03": "var(--v2-grey-400)",
"v2-background-bg-inverse": "var(--v2-grey-1000)",
"v2-background-bg-contrast": "var(--v2-grey-900)",
"v2-background-bg-button-neutral": "var(--v2-grey-100)",
"v2-background-bg-accent": "var(--v2-blue-600)",
"v2-text-text-base": "var(--v2-grey-1000)",
"v2-text-text-muted": "var(--v2-grey-700)",
"v2-text-text-faint": "var(--v2-grey-600)",
"v2-text-text-inverse": "var(--v2-grey-100)",
"v2-text-text-contrast": "var(--v2-grey-100)",
"v2-text-text-accent": "var(--v2-blue-600)",
"v2-text-text-accent-hover": "var(--v2-blue-700)",
"v2-icon-icon-base": "var(--v2-grey-800)",
"v2-icon-icon-muted": "var(--v2-grey-600)",
"v2-icon-icon-inverse": "var(--v2-grey-100)",
"v2-icon-icon-contrast": "var(--v2-grey-200)",
"v2-icon-icon-accent": "var(--v2-blue-600)",
"v2-icon-icon-accent-hover": "var(--v2-blue-700)",
"v2-border-border-muted": "var(--v2-alpha-dark-8)",
"v2-border-border-base": "var(--v2-alpha-dark-10)",
"v2-border-border-strong": "var(--v2-alpha-dark-20)",
"v2-border-border-inverse": "var(--v2-grey-1000)",
"v2-border-border-focus": "var(--v2-blue-500)",
"v2-overlay-simple-overlay-hover": "var(--v2-alpha-dark-4)",
"v2-overlay-simple-overlay-pressed": "var(--v2-alpha-dark-8)",
"v2-overlay-simple-overlay-contrast-hover": "var(--v2-alpha-light-12)",
"v2-overlay-simple-overlay-contrast-pressed": "var(--v2-alpha-light-24)",
"v2-overlay-simple-overlay-scrim": "var(--v2-alpha-dark-40)",
"v2-overlay-gradient-depth-overlay-depth-top": "var(--v2-alpha-light-100)",
"v2-overlay-gradient-depth-overlay-depth-bot": "var(--v2-alpha-light-0)",
"v2-overlay-simple-tab-active-scrim": "#fafafa00",
"v2-overlay-simple-tab-hover-scrim": "#eeeeee00",
"v2-overlay-simple-tab-scrim": "#fafafa00",
"v2-state-bg-success": "var(--v2-green-100)",
"v2-state-fg-success": "var(--v2-green-800)",
"v2-state-border-success": "var(--v2-green-300)",
"v2-state-bg-warning": "var(--v2-yellow-100)",
"v2-state-fg-warning": "var(--v2-yellow-800)",
"v2-state-border-warning": "var(--v2-yellow-300)",
"v2-state-bg-danger": "var(--v2-red-100)",
"v2-state-fg-danger": "var(--v2-red-800)",
"v2-state-border-danger": "var(--v2-red-300)",
"v2-state-bg-info": "var(--v2-blue-100)",
"v2-state-fg-info": "var(--v2-blue-800)",
"v2-state-border-info": "var(--v2-blue-300)",
"v2-avatar-bg-orange": "#ee7330ff",
"v2-avatar-border-orange": "#d16427ff",
"v2-avatar-bg-yellow": "#e7af36ff",
"v2-avatar-border-yellow": "#cb9f34ff",
"v2-avatar-bg-cyan": "#0096b8ff",
"v2-avatar-border-cyan": "#007d9bff",
"v2-avatar-bg-green": "#2eaf5aff",
"v2-avatar-border-green": "#198b43ff",
"v2-avatar-bg-red": "#d92e3cff",
"v2-avatar-border-red": "#b82d35ff",
"v2-avatar-bg-pink": "#e4429eff",
"v2-avatar-border-pink": "#c83d8bff",
"v2-avatar-bg-blue": "#3250dfff",
"v2-avatar-border-blue": "#2c47c8ff",
"v2-avatar-bg-purple": "#623be2ff",
"v2-avatar-border-purple": "#5230c2ff",
"v2-avatar-bg-gray": "#5c5c5cff",
"v2-avatar-border-gray": "#3a3a3aff",
"v2-elevation-raised": "0px 2px 4px 0px var(--v2-alpha-dark-4), 0px 1px 2px -1px var(--v2-alpha-dark-8), 0px 0px 0px 0.5px var(--v2-alpha-dark-12), 0px 0px 0px 0px var(--v2-alpha-dark-0)",
"v2-elevation-floating": "0px 8px 16px 0px var(--v2-alpha-dark-4), 0px 4px 8px 0px var(--v2-alpha-dark-8), 0px 0px 0px 0.5px var(--v2-alpha-dark-12), 0px 0px 0px 0px var(--v2-alpha-dark-0)",
"v2-elevation-overlay": "0px 16px 32px 0px var(--v2-alpha-dark-4), 0px 8px 16px 0px var(--v2-alpha-dark-8), 0px 0px 0px 0.5px var(--v2-alpha-dark-12), 0px 0px 0px 0px var(--v2-alpha-dark-0)",
"v2-elevation-button-neutral": "0px 1px 1.5px 0px var(--v2-alpha-dark-10), 0px 0px 0px 0.5px var(--v2-alpha-dark-14), 0px 0px 0px 0px var(--v2-alpha-dark-0)",
"v2-elevation-button-contrast": "0px 1px 1.5px 0px var(--v2-alpha-dark-20), 0px 0px 0px 0.5px var(--v2-grey-800), inset 0px 1px 2px 0px var(--v2-alpha-light-14), inset 0px -1px 2px 0px var(--v2-alpha-dark-6), 0px 0px 0px 0px var(--v2-alpha-dark-0)",
"v2-elevation-elements": "0px 0.5px 0.5px 0px var(--v2-alpha-dark-40)",
"v2-elevation-switch-off": "inset 0px 1px 1px 0px var(--v2-alpha-dark-8), inset 0px 0.5px 0.5px 0px var(--v2-alpha-dark-8), inset 0px 0px 0px 0.5px var(--v2-alpha-dark-10)",
"v2-elevation-switch-on": "inset 0px 2px 2px 0px var(--v2-alpha-dark-10), inset 0px 1px 1px 0px var(--v2-alpha-dark-10), inset 0px 0px 0px 0.5px var(--v2-alpha-dark-20)",
"v2-illustration-illustration-layer-01": "var(--v2-grey-300)",
"v2-illustration-illustration-layer-02": "var(--v2-grey-400)",
"v2-illustration-illustration-layer-03": "var(--v2-grey-500)"
}
},
"dark": {
@ -83,6 +271,194 @@
"syntax-diff-delete": "#fab283",
"syntax-diff-unknown": "#edb2f1",
"surface-critical-base": "#1F0603"
},
"v2Overrides": {
"v2-grey-100": "#ffffffff",
"v2-grey-200": "#fafafaff",
"v2-grey-300": "#eeeeeeff",
"v2-grey-400": "#d4d4d4ff",
"v2-grey-500": "#aeaeaeff",
"v2-grey-600": "#808080ff",
"v2-grey-700": "#5c5c5cff",
"v2-grey-800": "#3a3a3aff",
"v2-grey-900": "#242424ff",
"v2-grey-1000": "#161616ff",
"v2-grey-1100": "#080808ff",
"v2-grey-1200": "#000000ff",
"v2-red-100": "#fcecebff",
"v2-red-200": "#f6d5d3ff",
"v2-red-300": "#f2bbb7ff",
"v2-red-400": "#f29b96ff",
"v2-red-500": "#f17471ff",
"v2-red-600": "#f1484fff",
"v2-red-700": "#d92e3cff",
"v2-red-800": "#b82d35ff",
"v2-red-900": "#97252bff",
"v2-red-1000": "#7a1f23ff",
"v2-red-1100": "#5f1a1cff",
"v2-red-1200": "#461516ff",
"v2-orange-100": "#fdf2edff",
"v2-orange-200": "#ffe7dcff",
"v2-orange-300": "#ffd8c6ff",
"v2-orange-400": "#ffc1a4ff",
"v2-orange-500": "#ffa478ff",
"v2-orange-600": "#ff8648ff",
"v2-orange-700": "#ee7330ff",
"v2-orange-800": "#d16427ff",
"v2-orange-900": "#b35624ff",
"v2-orange-1000": "#954c27ff",
"v2-orange-1100": "#723d22ff",
"v2-orange-1200": "#5a2c14ff",
"v2-yellow-100": "#fefaecff",
"v2-yellow-200": "#fcefd0ff",
"v2-yellow-300": "#f7e5b5ff",
"v2-yellow-400": "#f3da9bff",
"v2-yellow-500": "#f2cf76ff",
"v2-yellow-600": "#f6c251ff",
"v2-yellow-700": "#e7af36ff",
"v2-yellow-800": "#cb9f34ff",
"v2-yellow-900": "#ac8833ff",
"v2-yellow-1000": "#8e7231ff",
"v2-yellow-1100": "#68552bff",
"v2-yellow-1200": "#4b4025ff",
"v2-green-100": "#e7f9eaff",
"v2-green-200": "#d0f0d5ff",
"v2-green-300": "#b8e9c1ff",
"v2-green-400": "#96e3a6ff",
"v2-green-500": "#6bd586ff",
"v2-green-600": "#49c970ff",
"v2-green-700": "#2eaf5aff",
"v2-green-800": "#198b43ff",
"v2-green-900": "#1d783cff",
"v2-green-1000": "#196130ff",
"v2-green-1100": "#164c26ff",
"v2-green-1200": "#14361dff",
"v2-cyan-100": "#e2f7fbff",
"v2-cyan-200": "#c4edf4ff",
"v2-cyan-300": "#a3e4efff",
"v2-cyan-400": "#65d9ebff",
"v2-cyan-500": "#00c5dfff",
"v2-cyan-600": "#00abcfff",
"v2-cyan-700": "#0096b8ff",
"v2-cyan-800": "#007d9bff",
"v2-cyan-900": "#006c85ff",
"v2-cyan-1000": "#005a6eff",
"v2-cyan-1100": "#004756ff",
"v2-cyan-1200": "#00353fff",
"v2-blue-100": "#ecf1feff",
"v2-blue-200": "#d7e2fcff",
"v2-blue-300": "#c3d4fdff",
"v2-blue-400": "#a2bcffff",
"v2-blue-500": "#7698fdff",
"v2-blue-600": "#3b5cf6ff",
"v2-blue-700": "#3250dfff",
"v2-blue-800": "#2c47c8ff",
"v2-blue-900": "#263fa9ff",
"v2-blue-1000": "#22388fff",
"v2-blue-1100": "#1c2e70ff",
"v2-blue-1200": "#1b2852ff",
"v2-purple-100": "#ebecfeff",
"v2-purple-200": "#d5d5fcff",
"v2-purple-300": "#b9b8f5ff",
"v2-purple-400": "#9e99f7ff",
"v2-purple-500": "#8271f8ff",
"v2-purple-600": "#7152f4ff",
"v2-purple-700": "#623be2ff",
"v2-purple-800": "#5230c2ff",
"v2-purple-900": "#442aa1ff",
"v2-purple-1000": "#361f83ff",
"v2-purple-1100": "#2b1b6aff",
"v2-purple-1200": "#221358ff",
"v2-pink-100": "#fdecf3ff",
"v2-pink-200": "#f7d5e4ff",
"v2-pink-300": "#fabcd8ff",
"v2-pink-400": "#f799c6ff",
"v2-pink-500": "#f26cb2ff",
"v2-pink-600": "#f64aabff",
"v2-pink-700": "#e4429eff",
"v2-pink-800": "#c83d8bff",
"v2-pink-900": "#aa3576ff",
"v2-pink-1000": "#8c2d61ff",
"v2-pink-1100": "#6f284fff",
"v2-pink-1200": "#5c1d3fff",
"v2-background-bg-base": "var(--v2-grey-1000)",
"v2-background-bg-deep": "var(--v2-grey-1100)",
"v2-background-bg-layer-01": "var(--v2-grey-900)",
"v2-background-bg-layer-02": "var(--v2-grey-800)",
"v2-background-bg-layer-03": "var(--v2-grey-700)",
"v2-background-bg-inverse": "var(--v2-grey-100)",
"v2-background-bg-contrast": "var(--v2-grey-700)",
"v2-background-bg-button-neutral": "var(--v2-alpha-light-6)",
"v2-background-bg-accent": "var(--v2-blue-600)",
"v2-text-text-base": "var(--v2-grey-200)",
"v2-text-text-muted": "var(--v2-grey-500)",
"v2-text-text-faint": "var(--v2-grey-600)",
"v2-text-text-inverse": "var(--v2-grey-1000)",
"v2-text-text-contrast": "var(--v2-grey-100)",
"v2-text-text-accent": "var(--v2-blue-400)",
"v2-text-text-accent-hover": "var(--v2-blue-300)",
"v2-icon-icon-base": "var(--v2-grey-400)",
"v2-icon-icon-muted": "var(--v2-grey-600)",
"v2-icon-icon-inverse": "var(--v2-grey-1000)",
"v2-icon-icon-contrast": "var(--v2-grey-200)",
"v2-icon-icon-accent": "var(--v2-blue-400)",
"v2-icon-icon-accent-hover": "var(--v2-blue-300)",
"v2-border-border-muted": "var(--v2-alpha-light-8)",
"v2-border-border-base": "var(--v2-alpha-light-10)",
"v2-border-border-strong": "var(--v2-alpha-light-20)",
"v2-border-border-inverse": "var(--v2-grey-100)",
"v2-border-border-focus": "var(--v2-blue-500)",
"v2-overlay-simple-overlay-hover": "var(--v2-alpha-light-6)",
"v2-overlay-simple-overlay-pressed": "var(--v2-alpha-light-10)",
"v2-overlay-simple-overlay-contrast-hover": "var(--v2-alpha-dark-24)",
"v2-overlay-simple-overlay-contrast-pressed": "var(--v2-alpha-dark-40)",
"v2-overlay-simple-overlay-scrim": "var(--v2-alpha-light-30)",
"v2-overlay-gradient-depth-overlay-depth-top": "var(--v2-alpha-light-100)",
"v2-overlay-gradient-depth-overlay-depth-bot": "var(--v2-alpha-light-0)",
"v2-overlay-simple-tab-active-scrim": "#24242400",
"v2-overlay-simple-tab-hover-scrim": "#3a3a3a00",
"v2-overlay-simple-tab-scrim": "#08080800",
"v2-state-bg-success": "var(--v2-green-1200)",
"v2-state-fg-success": "var(--v2-green-500)",
"v2-state-border-success": "var(--v2-green-900)",
"v2-state-bg-warning": "var(--v2-yellow-1200)",
"v2-state-fg-warning": "var(--v2-yellow-500)",
"v2-state-border-warning": "var(--v2-yellow-900)",
"v2-state-bg-danger": "var(--v2-red-1200)",
"v2-state-fg-danger": "var(--v2-red-500)",
"v2-state-border-danger": "var(--v2-red-900)",
"v2-state-bg-info": "var(--v2-blue-1200)",
"v2-state-fg-info": "var(--v2-blue-500)",
"v2-state-border-info": "var(--v2-blue-900)",
"v2-avatar-bg-orange": "#723d22ff",
"v2-avatar-border-orange": "#ff8648ff",
"v2-avatar-bg-yellow": "#68552bff",
"v2-avatar-border-yellow": "#e7af36ff",
"v2-avatar-bg-cyan": "#005a6eff",
"v2-avatar-border-cyan": "#0096b8ff",
"v2-avatar-bg-green": "#196130ff",
"v2-avatar-border-green": "#49c970ff",
"v2-avatar-bg-red": "#7a1f23ff",
"v2-avatar-border-red": "#d92e3cff",
"v2-avatar-bg-pink": "#8c2d61ff",
"v2-avatar-border-pink": "#e4429eff",
"v2-avatar-bg-blue": "#263fa9ff",
"v2-avatar-border-blue": "#7698fdff",
"v2-avatar-bg-purple": "#361f83ff",
"v2-avatar-border-purple": "#7152f4ff",
"v2-avatar-bg-gray": "#5c5c5cff",
"v2-avatar-border-gray": "#aeaeaeff",
"v2-elevation-raised": "0px 2px 4px 0px var(--v2-alpha-dark-30), 0px 1px 2px 0px var(--v2-alpha-dark-30), 0px 0px 0px 0.5px var(--v2-alpha-light-16), 0px -0.5px 0px 0px var(--v2-alpha-light-6)",
"v2-elevation-floating": "0px 8px 16px 0px var(--v2-alpha-dark-30), 0px 4px 8px 0px var(--v2-alpha-dark-30), 0px 0px 0px 0.5px var(--v2-alpha-light-16), 0px -0.5px 0px 0px var(--v2-alpha-light-6)",
"v2-elevation-overlay": "0px 16px 32px 0px var(--v2-alpha-dark-30), 0px 8px 16px 0px var(--v2-alpha-dark-30), 0px 0px 0px 0.5px var(--v2-alpha-light-16), 0px -0.5px 0px 0px var(--v2-alpha-light-6)",
"v2-elevation-button-neutral": "0px 1px 2px 0px var(--v2-alpha-dark-40), 0px 0px 0px 0.5px var(--v2-alpha-light-20), 0px -0.5px 0px 0px var(--v2-alpha-light-10)",
"v2-elevation-button-contrast": "0px 1px 2px 0px var(--v2-alpha-dark-40), 0px 0px 0px 0.5px var(--v2-alpha-light-40), inset 0px 0px 0px 0px var(--v2-alpha-light-0), inset 0px 0px 0px 0px var(--v2-alpha-light-0), 0px -0.5px 0px 0px var(--v2-alpha-light-30)",
"v2-elevation-elements": "0px 0.5px 0.5px 0px var(--v2-alpha-dark-40)",
"v2-elevation-switch-off": "inset 0px -0.5px 0px 0px var(--v2-alpha-light-10), inset 0px 0px 0px 0px var(--v2-alpha-light-0), inset 0px 0px 0px 0.5px var(--v2-alpha-light-16)",
"v2-elevation-switch-on": "inset 0px -0.5px 0px 0px var(--v2-alpha-light-10), inset 0px 0px 0px 0px var(--v2-alpha-light-0), inset 0px 0px 0px 0.5px var(--v2-alpha-light-16)",
"v2-illustration-illustration-layer-01": "var(--v2-grey-900)",
"v2-illustration-illustration-layer-02": "var(--v2-grey-800)",
"v2-illustration-illustration-layer-03": "var(--v2-grey-700)"
}
}
}

View file

@ -34,6 +34,7 @@ export interface ThemePaletteColors {
type ThemeVariantBase = {
overrides?: Record<string, ColorValue>
v2Overrides?: Record<string, V2ColorValue>
}
export type ThemeVariant =
@ -67,4 +68,8 @@ export type CssVarRef = `var(--${string})`
export type ColorValue = HexColor | CssVarRef
export type V2ColorValue = HexColor | CssVarRef | string
export type ResolvedTheme = Record<ThemeToken, ColorValue>
export type ResolvedV2Theme = Record<string, V2ColorValue>

View file

@ -0,0 +1,48 @@
import type { V2ColorValue } from "../types"
/** Fixed project avatar colors (OC-2); theme-independent like v1 `avatar-background-*`. */
export const V2_AVATAR_FG = "#ffffffff"
export const V2_AVATAR_LIGHT: Record<string, V2ColorValue> = {
"v2-avatar-fg": V2_AVATAR_FG,
"v2-avatar-bg-orange": "#ee7330ff",
"v2-avatar-border-orange": "#d16427ff",
"v2-avatar-bg-yellow": "#e7af36ff",
"v2-avatar-border-yellow": "#cb9f34ff",
"v2-avatar-bg-cyan": "#0096b8ff",
"v2-avatar-border-cyan": "#007d9bff",
"v2-avatar-bg-green": "#2eaf5aff",
"v2-avatar-border-green": "#198b43ff",
"v2-avatar-bg-red": "#d92e3cff",
"v2-avatar-border-red": "#b82d35ff",
"v2-avatar-bg-pink": "#e4429eff",
"v2-avatar-border-pink": "#c83d8bff",
"v2-avatar-bg-blue": "#3250dfff",
"v2-avatar-border-blue": "#2c47c8ff",
"v2-avatar-bg-purple": "#623be2ff",
"v2-avatar-border-purple": "#5230c2ff",
"v2-avatar-bg-gray": "#5c5c5cff",
"v2-avatar-border-gray": "#3a3a3aff",
}
export const V2_AVATAR_DARK: Record<string, V2ColorValue> = {
"v2-avatar-fg": V2_AVATAR_FG,
"v2-avatar-bg-orange": "#723d22ff",
"v2-avatar-border-orange": "#ff8648ff",
"v2-avatar-bg-yellow": "#68552bff",
"v2-avatar-border-yellow": "#e7af36ff",
"v2-avatar-bg-cyan": "#005a6eff",
"v2-avatar-border-cyan": "#0096b8ff",
"v2-avatar-bg-green": "#196130ff",
"v2-avatar-border-green": "#49c970ff",
"v2-avatar-bg-red": "#7a1f23ff",
"v2-avatar-border-red": "#d92e3cff",
"v2-avatar-bg-pink": "#8c2d61ff",
"v2-avatar-border-pink": "#e4429eff",
"v2-avatar-bg-blue": "#263fa9ff",
"v2-avatar-border-blue": "#7698fdff",
"v2-avatar-bg-purple": "#361f83ff",
"v2-avatar-border-purple": "#7152f4ff",
"v2-avatar-bg-gray": "#5c5c5cff",
"v2-avatar-border-gray": "#aeaeaeff",
}

View file

@ -0,0 +1,113 @@
import type { V2ColorValue } from "../types"
/** Default v2 hue ramps from `v2/styles/colors.css` (OC-2). Alpha ramps live in CSS only. */
export const V2_PRIMITIVES_DEFAULT: Record<string, V2ColorValue> = {
"v2-grey-100": "#ffffffff",
"v2-grey-200": "#fafafaff",
"v2-grey-300": "#eeeeeeff",
"v2-grey-400": "#d4d4d4ff",
"v2-grey-500": "#aeaeaeff",
"v2-grey-600": "#808080ff",
"v2-grey-700": "#5c5c5cff",
"v2-grey-800": "#3a3a3aff",
"v2-grey-900": "#242424ff",
"v2-grey-1000": "#161616ff",
"v2-grey-1100": "#080808ff",
"v2-grey-1200": "#000000ff",
"v2-red-100": "#fcecebff",
"v2-red-200": "#f6d5d3ff",
"v2-red-300": "#f2bbb7ff",
"v2-red-400": "#f29b96ff",
"v2-red-500": "#f17471ff",
"v2-red-600": "#f1484fff",
"v2-red-700": "#d92e3cff",
"v2-red-800": "#b82d35ff",
"v2-red-900": "#97252bff",
"v2-red-1000": "#7a1f23ff",
"v2-red-1100": "#5f1a1cff",
"v2-red-1200": "#461516ff",
"v2-orange-100": "#fdf2edff",
"v2-orange-200": "#ffe7dcff",
"v2-orange-300": "#ffd8c6ff",
"v2-orange-400": "#ffc1a4ff",
"v2-orange-500": "#ffa478ff",
"v2-orange-600": "#ff8648ff",
"v2-orange-700": "#ee7330ff",
"v2-orange-800": "#d16427ff",
"v2-orange-900": "#b35624ff",
"v2-orange-1000": "#954c27ff",
"v2-orange-1100": "#723d22ff",
"v2-orange-1200": "#5a2c14ff",
"v2-yellow-100": "#fefaecff",
"v2-yellow-200": "#fcefd0ff",
"v2-yellow-300": "#f7e5b5ff",
"v2-yellow-400": "#f3da9bff",
"v2-yellow-500": "#f2cf76ff",
"v2-yellow-600": "#f6c251ff",
"v2-yellow-700": "#e7af36ff",
"v2-yellow-800": "#cb9f34ff",
"v2-yellow-900": "#ac8833ff",
"v2-yellow-1000": "#8e7231ff",
"v2-yellow-1100": "#68552bff",
"v2-yellow-1200": "#4b4025ff",
"v2-green-100": "#e7f9eaff",
"v2-green-200": "#d0f0d5ff",
"v2-green-300": "#b8e9c1ff",
"v2-green-400": "#96e3a6ff",
"v2-green-500": "#6bd586ff",
"v2-green-600": "#49c970ff",
"v2-green-700": "#2eaf5aff",
"v2-green-800": "#198b43ff",
"v2-green-900": "#1d783cff",
"v2-green-1000": "#196130ff",
"v2-green-1100": "#164c26ff",
"v2-green-1200": "#14361dff",
"v2-cyan-100": "#e2f7fbff",
"v2-cyan-200": "#c4edf4ff",
"v2-cyan-300": "#a3e4efff",
"v2-cyan-400": "#65d9ebff",
"v2-cyan-500": "#00c5dfff",
"v2-cyan-600": "#00abcfff",
"v2-cyan-700": "#0096b8ff",
"v2-cyan-800": "#007d9bff",
"v2-cyan-900": "#006c85ff",
"v2-cyan-1000": "#005a6eff",
"v2-cyan-1100": "#004756ff",
"v2-cyan-1200": "#00353fff",
"v2-blue-100": "#ecf1feff",
"v2-blue-200": "#d7e2fcff",
"v2-blue-300": "#c3d4fdff",
"v2-blue-400": "#a2bcffff",
"v2-blue-500": "#7698fdff",
"v2-blue-600": "#3b5cf6ff",
"v2-blue-700": "#3250dfff",
"v2-blue-800": "#2c47c8ff",
"v2-blue-900": "#263fa9ff",
"v2-blue-1000": "#22388fff",
"v2-blue-1100": "#1c2e70ff",
"v2-blue-1200": "#1b2852ff",
"v2-purple-100": "#ebecfeff",
"v2-purple-200": "#d5d5fcff",
"v2-purple-300": "#b9b8f5ff",
"v2-purple-400": "#9e99f7ff",
"v2-purple-500": "#8271f8ff",
"v2-purple-600": "#7152f4ff",
"v2-purple-700": "#623be2ff",
"v2-purple-800": "#5230c2ff",
"v2-purple-900": "#442aa1ff",
"v2-purple-1000": "#361f83ff",
"v2-purple-1100": "#2b1b6aff",
"v2-purple-1200": "#221358ff",
"v2-pink-100": "#fdecf3ff",
"v2-pink-200": "#f7d5e4ff",
"v2-pink-300": "#fabcd8ff",
"v2-pink-400": "#f799c6ff",
"v2-pink-500": "#f26cb2ff",
"v2-pink-600": "#f64aabff",
"v2-pink-700": "#e4429eff",
"v2-pink-800": "#c83d8bff",
"v2-pink-900": "#aa3576ff",
"v2-pink-1000": "#8c2d61ff",
"v2-pink-1100": "#6f284fff",
"v2-pink-1200": "#5c1d3fff",
}

View file

@ -0,0 +1,24 @@
import { blend, hexToOklch, shift } from "../color"
import type { ColorValue, HexColor, V2ColorValue } from "../types"
export function mapV2Foreground(
ink: HexColor,
isDark: boolean,
overrides: Record<string, ColorValue> = {},
): Record<string, V2ColorValue> {
const tint = hexToOklch(ink)
const body = shift(ink, {
l: isDark ? Math.max(0, 0.88 - tint.l) * 0.4 : -Math.max(0, tint.l - 0.18) * 0.24,
c: isDark ? 1.04 : 1.02,
})
return {
"v2-text-text-base": isDark
? blend("#ffffff", body, 0.9)
: shift(body, { l: -0.07, c: 1.04 }),
"v2-text-text-muted":
overrides["text-weak"] ??
shift(body, { l: isDark ? -0.11 : 0.11, c: 0.9 }),
"v2-text-text-faint": shift(body, { l: isDark ? -0.2 : 0.21, c: isDark ? 0.78 : 0.72 }),
}
}

View file

@ -0,0 +1,150 @@
import type { V2ColorValue } from "../types"
import { V2_AVATAR_DARK, V2_AVATAR_LIGHT } from "./avatar"
const ref = (name: string): V2ColorValue => `var(--${name})`
const light: Record<string, V2ColorValue> = {
"v2-background-bg-base": ref("v2-grey-100"),
"v2-background-bg-deep": ref("v2-grey-200"),
"v2-background-bg-layer-01": ref("v2-grey-300"),
"v2-background-bg-layer-02": ref("v2-grey-400"),
"v2-background-bg-layer-03": ref("v2-grey-500"),
"v2-background-bg-inverse": ref("v2-grey-1000"),
"v2-background-bg-contrast": ref("v2-grey-900"),
"v2-background-bg-button-neutral": ref("v2-grey-100"),
"v2-background-bg-accent": ref("v2-blue-600"),
"v2-text-text-inverse": ref("v2-grey-100"),
"v2-text-text-contrast": ref("v2-grey-100"),
"v2-text-text-accent": ref("v2-blue-600"),
"v2-text-text-accent-hover": ref("v2-blue-700"),
"v2-icon-icon-base": ref("v2-grey-1000"),
"v2-icon-icon-muted": ref("v2-grey-800"),
"v2-icon-icon-inverse": ref("v2-grey-100"),
"v2-icon-icon-contrast": ref("v2-grey-200"),
"v2-icon-icon-accent": ref("v2-blue-600"),
"v2-icon-icon-accent-hover": ref("v2-blue-700"),
"v2-border-border-muted": ref("v2-alpha-dark-8"),
"v2-border-border-base": ref("v2-alpha-dark-10"),
"v2-border-border-strong": ref("v2-alpha-dark-20"),
"v2-border-border-inverse": ref("v2-grey-1000"),
"v2-border-border-focus": ref("v2-blue-500"),
"v2-overlay-simple-overlay-hover": ref("v2-alpha-dark-4"),
"v2-overlay-simple-overlay-pressed": ref("v2-alpha-dark-8"),
"v2-overlay-simple-overlay-contrast-hover": ref("v2-alpha-light-12"),
"v2-overlay-simple-overlay-contrast-pressed": ref("v2-alpha-light-24"),
"v2-overlay-simple-overlay-scrim": ref("v2-alpha-dark-40"),
"v2-overlay-gradient-depth-overlay-depth-top": ref("v2-alpha-light-100"),
"v2-overlay-gradient-depth-overlay-depth-bot": ref("v2-alpha-light-0"),
"v2-overlay-simple-tab-active-scrim": "#fafafa00",
"v2-overlay-simple-tab-hover-scrim": "#eeeeee00",
"v2-overlay-simple-tab-scrim": "#fafafa00",
"v2-state-bg-success": ref("v2-green-100"),
"v2-state-fg-success": ref("v2-green-800"),
"v2-state-border-success": ref("v2-green-300"),
"v2-state-bg-warning": ref("v2-yellow-100"),
"v2-state-fg-warning": ref("v2-yellow-800"),
"v2-state-border-warning": ref("v2-yellow-300"),
"v2-state-bg-danger": ref("v2-red-100"),
"v2-state-fg-danger": ref("v2-red-800"),
"v2-state-border-danger": ref("v2-red-300"),
"v2-state-bg-info": ref("v2-blue-100"),
"v2-state-fg-info": ref("v2-blue-800"),
"v2-state-border-info": ref("v2-blue-300"),
...V2_AVATAR_LIGHT,
"v2-elevation-raised":
"0px 2px 4px 0px var(--v2-alpha-dark-4), 0px 1px 2px -1px var(--v2-alpha-dark-8), 0px 0px 0px 0.5px var(--v2-alpha-dark-12), 0px 0px 0px 0px var(--v2-alpha-dark-0)",
"v2-elevation-floating":
"0px 8px 16px 0px var(--v2-alpha-dark-4), 0px 4px 8px 0px var(--v2-alpha-dark-8), 0px 0px 0px 0.5px var(--v2-alpha-dark-12), 0px 0px 0px 0px var(--v2-alpha-dark-0)",
"v2-elevation-overlay":
"0px 16px 32px 0px var(--v2-alpha-dark-4), 0px 8px 16px 0px var(--v2-alpha-dark-8), 0px 0px 0px 0.5px var(--v2-alpha-dark-12), 0px 0px 0px 0px var(--v2-alpha-dark-0)",
"v2-elevation-button-neutral":
"0px 1px 1.5px 0px var(--v2-alpha-dark-10), 0px 0px 0px 0.5px var(--v2-alpha-dark-14), 0px 0px 0px 0px var(--v2-alpha-dark-0)",
"v2-elevation-button-contrast":
"0px 1px 1.5px 0px var(--v2-alpha-dark-20), 0px 0px 0px 0.5px var(--v2-grey-800), inset 0px 1px 2px 0px var(--v2-alpha-light-14), inset 0px -1px 2px 0px var(--v2-alpha-dark-6), 0px 0px 0px 0px var(--v2-alpha-dark-0)",
"v2-elevation-elements": "0px 0.5px 0.5px 0px var(--v2-alpha-dark-40)",
"v2-elevation-switch-off":
"inset 0px 1px 1px 0px var(--v2-alpha-dark-8), inset 0px 0.5px 0.5px 0px var(--v2-alpha-dark-8), inset 0px 0px 0px 0.5px var(--v2-alpha-dark-10)",
"v2-elevation-switch-on":
"inset 0px 2px 2px 0px var(--v2-alpha-dark-10), inset 0px 1px 1px 0px var(--v2-alpha-dark-10), inset 0px 0px 0px 0.5px var(--v2-alpha-dark-20)",
"v2-illustration-illustration-layer-01": ref("v2-grey-300"),
"v2-illustration-illustration-layer-02": ref("v2-grey-400"),
"v2-illustration-illustration-layer-03": ref("v2-grey-500"),
}
const dark: Record<string, V2ColorValue> = {
"v2-background-bg-base": ref("v2-grey-1000"),
"v2-background-bg-deep": ref("v2-grey-1100"),
"v2-background-bg-layer-01": ref("v2-grey-800"),
"v2-background-bg-layer-02": ref("v2-grey-600"),
"v2-background-bg-layer-03": ref("v2-grey-500"),
"v2-background-bg-inverse": ref("v2-grey-100"),
"v2-background-bg-contrast": ref("v2-grey-700"),
"v2-background-bg-button-neutral": ref("v2-alpha-light-6"),
"v2-background-bg-accent": ref("v2-blue-600"),
"v2-text-text-inverse": ref("v2-grey-1000"),
"v2-text-text-contrast": ref("v2-grey-100"),
"v2-text-text-accent": ref("v2-blue-400"),
"v2-text-text-accent-hover": ref("v2-blue-300"),
"v2-icon-icon-base": ref("v2-grey-300"),
"v2-icon-icon-muted": ref("v2-grey-400"),
"v2-icon-icon-inverse": ref("v2-grey-1000"),
"v2-icon-icon-contrast": ref("v2-grey-200"),
"v2-icon-icon-accent": ref("v2-blue-400"),
"v2-icon-icon-accent-hover": ref("v2-blue-300"),
"v2-border-border-muted": ref("v2-alpha-light-8"),
"v2-border-border-base": ref("v2-alpha-light-10"),
"v2-border-border-strong": ref("v2-alpha-light-20"),
"v2-border-border-inverse": ref("v2-grey-100"),
"v2-border-border-focus": ref("v2-blue-500"),
"v2-overlay-simple-overlay-hover": ref("v2-alpha-light-6"),
"v2-overlay-simple-overlay-pressed": ref("v2-alpha-light-10"),
"v2-overlay-simple-overlay-contrast-hover": ref("v2-alpha-dark-24"),
"v2-overlay-simple-overlay-contrast-pressed": ref("v2-alpha-dark-40"),
"v2-overlay-simple-overlay-scrim": ref("v2-alpha-light-30"),
"v2-overlay-gradient-depth-overlay-depth-top": ref("v2-alpha-light-100"),
"v2-overlay-gradient-depth-overlay-depth-bot": ref("v2-alpha-light-0"),
"v2-overlay-simple-tab-active-scrim": "#24242400",
"v2-overlay-simple-tab-hover-scrim": "#3a3a3a00",
"v2-overlay-simple-tab-scrim": "#08080800",
"v2-state-bg-success": ref("v2-green-1200"),
"v2-state-fg-success": ref("v2-green-500"),
"v2-state-border-success": ref("v2-green-900"),
"v2-state-bg-warning": ref("v2-yellow-1200"),
"v2-state-fg-warning": ref("v2-yellow-500"),
"v2-state-border-warning": ref("v2-yellow-900"),
"v2-state-bg-danger": ref("v2-red-1200"),
"v2-state-fg-danger": ref("v2-red-500"),
"v2-state-border-danger": ref("v2-red-900"),
"v2-state-bg-info": ref("v2-blue-1200"),
"v2-state-fg-info": ref("v2-blue-500"),
"v2-state-border-info": ref("v2-blue-900"),
...V2_AVATAR_DARK,
"v2-elevation-raised":
"0px 2px 4px 0px var(--v2-alpha-dark-30), 0px 1px 2px 0px var(--v2-alpha-dark-30), 0px 0px 0px 0.5px var(--v2-alpha-light-16), 0px -0.5px 0px 0px var(--v2-alpha-light-6)",
"v2-elevation-floating":
"0px 8px 16px 0px var(--v2-alpha-dark-30), 0px 4px 8px 0px var(--v2-alpha-dark-30), 0px 0px 0px 0.5px var(--v2-alpha-light-16), 0px -0.5px 0px 0px var(--v2-alpha-light-6)",
"v2-elevation-overlay":
"0px 16px 32px 0px var(--v2-alpha-dark-30), 0px 8px 16px 0px var(--v2-alpha-dark-30), 0px 0px 0px 0.5px var(--v2-alpha-light-16), 0px -0.5px 0px 0px var(--v2-alpha-light-6)",
"v2-elevation-button-neutral":
"0px 1px 2px 0px var(--v2-alpha-dark-40), 0px 0px 0px 0.5px var(--v2-alpha-light-20), 0px -0.5px 0px 0px var(--v2-alpha-light-10)",
"v2-elevation-button-contrast":
"0px 1px 2px 0px var(--v2-alpha-dark-40), 0px 0px 0px 0.5px var(--v2-alpha-light-40), inset 0px 0px 0px 0px var(--v2-alpha-light-0), inset 0px 0px 0px 0px var(--v2-alpha-light-0), 0px -0.5px 0px 0px var(--v2-alpha-light-30)",
"v2-elevation-elements": "0px 0.5px 0.5px 0px var(--v2-alpha-dark-40)",
"v2-elevation-switch-off":
"inset 0px -0.5px 0px 0px var(--v2-alpha-light-10), inset 0px 0px 0px 0px var(--v2-alpha-light-0), inset 0px 0px 0px 0.5px var(--v2-alpha-light-16)",
"v2-elevation-switch-on":
"inset 0px -0.5px 0px 0px var(--v2-alpha-light-10), inset 0px 0px 0px 0px var(--v2-alpha-light-0), inset 0px 0px 0px 0.5px var(--v2-alpha-light-16)",
"v2-illustration-illustration-layer-01": ref("v2-grey-900"),
"v2-illustration-illustration-layer-02": ref("v2-grey-800"),
"v2-illustration-illustration-layer-03": ref("v2-grey-700"),
}
export function mapV2Semantics(isDark: boolean): Record<string, V2ColorValue> {
return isDark ? dark : light
}
export function mergeV2Tokens(
...layers: Record<string, V2ColorValue>[]
): Record<string, V2ColorValue> {
return Object.assign({}, ...layers)
}

View file

@ -0,0 +1,153 @@
// @refresh reload
import { generateNeutralScale, hexToOklch, oklchToHex, shift } from "../color"
import { mapV2Foreground } from "./foreground"
import { mapV2Semantics, mergeV2Tokens } from "./mapping"
import type { DesktopTheme, HexColor, ResolvedV2Theme, ThemeVariant, V2ColorValue } from "../types"
import { V2_PRIMITIVES_DEFAULT } from "./default-primitives"
const V2_STEPS = [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200] as const
interface PaletteInput {
neutral: HexColor
ink: HexColor
primary: HexColor
accent: HexColor
success: HexColor
warning: HexColor
error: HexColor
info: HexColor
interactive: HexColor
diffAdd: HexColor
diffDelete: HexColor
}
function clamp(v: number, min: number, max: number) {
return Math.max(min, Math.min(max, v))
}
/** v2 ramps: 100 = lightest, 1200 = darkest — wider spread than v1 `generateScale`. */
function generateV2HueScale(seed: HexColor, isDark: boolean): HexColor[] {
const base = hexToOklch(seed)
const chromaBoost = isDark ? 1 : 1.05
const lightSteps = [
0.99,
0.965,
0.93,
0.885,
0.835,
clamp(base.l, 0.48, 0.72),
clamp(base.l - 0.07, 0.4, 0.64),
clamp(base.l - 0.14, 0.32, 0.55),
clamp(base.l - 0.21, 0.24, 0.46),
clamp(base.l - 0.28, 0.17, 0.38),
clamp(base.l - 0.34, 0.12, 0.3),
clamp(base.l - 0.4, 0.08, 0.22),
]
const chromaMultipliers = [0.28, 0.48, 0.68, 0.86, 1.02, 1.28, 1.34, 1.28, 1.18, 1.08, 0.98, 0.88]
return lightSteps.map((l, i) =>
oklchToHex({
l,
c: base.c * chromaMultipliers[i]! * chromaBoost,
h: base.h,
}),
)
}
/** Grey ramp: 100 = lightest, 1200 = darkest. Derived from palette neutral → ink like v1. */
function generateV2NeutralScale(neutral: HexColor, ink: HexColor, isDark: boolean): HexColor[] {
const scale = generateNeutralScale(neutral, isDark, ink)
return isDark ? scale.toReversed() : scale
}
function assignHueRamp(prefix: string, scale: HexColor[]): Record<string, V2ColorValue> {
const tokens: Record<string, V2ColorValue> = {}
for (let i = 0; i < V2_STEPS.length; i++) {
tokens[`v2-${prefix}-${V2_STEPS[i]}`] = scale[i]!
}
return tokens
}
function readPalette(variant: ThemeVariant): PaletteInput {
if ("palette" in variant && variant.palette) {
const palette = variant.palette
return {
neutral: palette.neutral,
ink: palette.ink,
primary: palette.primary,
accent: palette.accent ?? palette.info,
success: palette.success,
warning: palette.warning,
error: palette.error,
info: palette.info,
interactive: palette.interactive ?? palette.primary,
diffAdd: palette.diffAdd ?? shift(palette.success, { c: 0.55, l: 0.14 }),
diffDelete: palette.diffDelete ?? palette.error,
}
}
if ("seeds" in variant && variant.seeds) {
const seeds = variant.seeds
return {
neutral: seeds.neutral,
ink: seeds.neutral,
primary: seeds.primary,
accent: seeds.info,
success: seeds.success,
warning: seeds.warning,
error: seeds.error,
info: seeds.info,
interactive: seeds.interactive,
diffAdd: seeds.diffAdd,
diffDelete: seeds.diffDelete,
}
}
throw new Error("Theme variant requires `palette` or `seeds`")
}
/** Build v2 primitive ramps (100 = lightest). Alpha ramps are static in `v2/styles/colors.css`. */
export function generateV2Primitives(variant: ThemeVariant, isDark: boolean): Record<string, V2ColorValue> {
const colors = readPalette(variant)
const grey = generateV2NeutralScale(colors.neutral, colors.ink, isDark)
const blue = generateV2HueScale(colors.interactive, isDark)
const green = generateV2HueScale(colors.success, isDark)
const yellow = generateV2HueScale(colors.warning, isDark)
const red = generateV2HueScale(colors.error, isDark)
const purple = generateV2HueScale(colors.accent, isDark)
const pink = generateV2HueScale(colors.info, isDark)
const orange = generateV2HueScale(shift(colors.warning, { h: -22, l: -0.082, c: 0.94 }), isDark)
const cyan = generateV2HueScale(shift(colors.info, { h: -12, l: 0.128, c: 1.12 }), isDark)
return {
...V2_PRIMITIVES_DEFAULT,
...assignHueRamp("grey", grey),
...assignHueRamp("blue", blue),
...assignHueRamp("green", green),
...assignHueRamp("yellow", yellow),
...assignHueRamp("red", red),
...assignHueRamp("purple", purple),
...assignHueRamp("pink", pink),
...assignHueRamp("orange", orange),
...assignHueRamp("cyan", cyan),
}
}
export function resolveThemeVariantV2(variant: ThemeVariant, isDark: boolean): ResolvedV2Theme {
const primitives = generateV2Primitives(variant, isDark)
const semantics = mapV2Semantics(isDark)
const foreground = mapV2Foreground(readPalette(variant).ink, isDark, variant.overrides)
return mergeV2Tokens(primitives, semantics, foreground, variant.v2Overrides ?? {})
}
export function resolveThemeV2(theme: DesktopTheme): { light: ResolvedV2Theme; dark: ResolvedV2Theme } {
return {
light: resolveThemeVariantV2(theme.light, false),
dark: resolveThemeVariantV2(theme.dark, true),
}
}
export function themeV2ToCss(tokens: ResolvedV2Theme): string {
return Object.entries(tokens)
.map(([key, value]) => `--${key}: ${value};`)
.join("\n ")
}