diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml new file mode 100644 index 0000000..b5ba770 --- /dev/null +++ b/frontend/pnpm-lock.yaml @@ -0,0 +1,48 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@types/react': + specifier: ^19.0.0 + version: 19.2.14 + react: + specifier: ^19.0.0 + version: 19.2.5 + typescript: + specifier: ^5.7.0 + version: 5.9.3 + +packages: + + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + react@19.2.5: + resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} + engines: {node: '>=0.10.0'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + +snapshots: + + '@types/react@19.2.14': + dependencies: + csstype: 3.2.3 + + csstype@3.2.3: {} + + react@19.2.5: {} + + typescript@5.9.3: {} diff --git a/frontend/src/css.ts b/frontend/src/css.ts index ab28642..e3150d8 100644 --- a/frontend/src/css.ts +++ b/frontend/src/css.ts @@ -210,6 +210,7 @@ export function createTailwindThemeBridge(options: TailwindBridgeOptions = {}) { md: `var(${foundationVars.radiusMd})`, lg: `var(${foundationVars.radiusLg})`, xl: `var(${foundationVars.radiusXl})`, + field: `var(${foundationVars.fieldRadius})`, }, fontFamily: { sans: `var(${staticVars.fontSans})`, diff --git a/frontend/src/helpers.ts b/frontend/src/helpers.ts index c21d656..29f44fa 100644 --- a/frontend/src/helpers.ts +++ b/frontend/src/helpers.ts @@ -15,7 +15,7 @@ const defaultStaticCssVarMap = createStaticCssVarMap(); * @example * cssVar('primary') // → 'var(--ag-primary, #3b82f6)' * cssVar('bgSurface') // → 'var(--ag-bg-surface, #1c2237)' - * cssVar('radiusMd') // → 'var(--ag-radius-md, 10px)' + * cssVar('fieldRadius') // → 'var(--ag-field-radius, 0.5rem)' */ export function cssVar(token: TokenName, options: CssVarOptions = {}): string { const themeCssVarMap = options.prefix ? createThemeCssVarMap(options) : defaultThemeCssVarMap; diff --git a/frontend/src/index.ts b/frontend/src/index.ts index 982904e..ab17108 100644 --- a/frontend/src/index.ts +++ b/frontend/src/index.ts @@ -46,3 +46,17 @@ export { // 插件 Helper export type { TokenName } from './helpers.js'; export { cssVar, themeStyle } from './helpers.js'; + +export type { + AccountFormProps, + PluginBatchAccountInput, + PluginBatchImportResult, + PluginFrontendModule, + PluginMenuItemDefinition, + PluginOAuthBatchExchangeResult, + PluginOAuthBridge, + PluginOAuthExchangeResult, + PluginOAuthStartResult, + PluginPlatformIconProps, + PluginRouteDefinition, +} from './plugin.js'; diff --git a/frontend/src/plugin.tsx b/frontend/src/plugin.tsx index 3b80a3d..57812a6 100644 --- a/frontend/src/plugin.tsx +++ b/frontend/src/plugin.tsx @@ -2,6 +2,8 @@ import { useLayoutEffect, useRef, type ButtonHTMLAttributes, + type ComponentType, + type CSSProperties, type InputHTMLAttributes, type ReactNode, type TextareaHTMLAttributes, @@ -42,6 +44,82 @@ export interface PluginTailwindConfigOptions { export type PluginStatusKind = 'info' | 'success' | 'error'; +export interface PluginOAuthStartResult { + authorizeURL: string; + state: string; +} + +export interface PluginOAuthExchangeResult { + accountType: string; + accountName: string; + credentials: Record; +} + +export interface PluginOAuthBatchExchangeResult { + accountType: string; + accountName: string; + credentials: Record; + status: 'ok' | 'failed'; + error?: string; +} + +export interface PluginOAuthBridge { + start: () => Promise; + exchange: (callbackURL: string) => Promise; + batchExchange?: (sessionKeys: string[]) => Promise; + importRefresh?: (refreshToken: string, clientId?: string) => Promise; + batchImportRefresh?: ( + refreshTokens: string[], + clientId?: string, + ) => Promise; +} + +export interface PluginBatchAccountInput { + name: string; + type: string; + credentials: Record; +} + +export interface PluginBatchImportResult { + imported: number; + failed: number; +} + +export interface AccountFormProps { + credentials: Record; + onChange: (credentials: Record) => void; + mode: 'create' | 'edit'; + accountType?: string; + onAccountTypeChange?: (type: string) => void; + onSuggestedName?: (name: string) => void; + onBatchModeChange?: (isBatch: boolean) => void; + onBatchImport?: (accounts: PluginBatchAccountInput[]) => Promise; + oauth?: PluginOAuthBridge; +} + +export interface PluginRouteDefinition { + path: string; + component: ComponentType; +} + +export interface PluginMenuItemDefinition { + path: string; + title: string; + icon: string; +} + +export interface PluginPlatformIconProps { + className?: string; + style?: CSSProperties; +} + +export interface PluginFrontendModule { + routes?: PluginRouteDefinition[]; + menuItems?: PluginMenuItemDefinition[]; + accountForm?: ComponentType; + platformIcon?: ComponentType; +} + export const pluginFoundationCssText = ` /* ── AirGate — Plugin Foundation ── */ @@ -53,7 +131,7 @@ export const pluginFoundationCssText = ` font-family: var(--ag-font-sans); font-size: 0.875rem; color: var(--ag-text); - letter-spacing: -0.01em; + letter-spacing: 0; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } @@ -89,7 +167,7 @@ export const pluginFoundationCssText = ` .agw-panel-title { font-size: 0.875rem; font-weight: 600; - letter-spacing: -0.02em; + letter-spacing: 0; color: var(--ag-text); } @@ -97,7 +175,7 @@ export const pluginFoundationCssText = ` font-size: 0.625rem; font-weight: 500; text-transform: uppercase; - letter-spacing: 0.1em; + letter-spacing: 0; color: var(--ag-text-tertiary); font-family: var(--ag-font-mono); } @@ -112,7 +190,7 @@ export const pluginFoundationCssText = ` font-size: 0.6875rem; font-weight: 500; text-transform: uppercase; - letter-spacing: 0.06em; + letter-spacing: 0; color: var(--ag-text-secondary); } @@ -130,28 +208,30 @@ export const pluginFoundationCssText = ` .agw-input { display: block; width: 100%; - border: 1px solid var(--ag-glass-border); - border-radius: var(--ag-radius-md); - background: var(--ag-bg-surface); + border: 1px solid color-mix(in oklab, var(--ag-border) 88%, transparent); + border-radius: var(--ag-field-radius, 0.5rem); + background: var(--ag-field-background); padding: 0.5rem 0.75rem; - color: var(--ag-text); + color: var(--ag-field-foreground); font-size: 0.875rem; outline: none; - transition: border-color 200ms, box-shadow 200ms, background-color 200ms; + box-shadow: var(--ag-shadow-sm); + transition: border-color var(--ag-transition), box-shadow var(--ag-transition), background-color var(--ag-transition); } .agw-input::placeholder { - color: var(--ag-text-tertiary); + color: var(--ag-field-placeholder); } .agw-input:hover { - border-color: var(--ag-border); + background: color-mix(in oklab, var(--ag-field-background) 86%, var(--ag-surface) 14%); + border-color: color-mix(in oklab, var(--ag-border) 92%, var(--ag-text) 8%); } .agw-input:focus, .agw-input:focus-visible { border-color: var(--ag-border-focus); - box-shadow: 0 0 0 3px var(--ag-primary-subtle); + box-shadow: 0 0 0 2px color-mix(in oklab, var(--ag-primary) 22%, transparent); } .agw-input-mono { @@ -164,20 +244,20 @@ export const pluginFoundationCssText = ` } .agw-card { - border: 1px solid var(--ag-glass-border); - border-radius: var(--ag-radius-lg); - background: var(--ag-bg-elevated); + border: 1px solid var(--ag-border); + border-radius: var(--ag-radius-sm); + background: var(--ag-surface); padding: 1rem; - transition: border-color 200ms, background-color 200ms, box-shadow 200ms; + transition: border-color var(--ag-transition), background-color var(--ag-transition), box-shadow var(--ag-transition); } .agw-status-inline { display: inline-flex; align-items: center; padding: 0.25rem 0.75rem; - border: 1px solid var(--ag-glass-border); - border-radius: 999px; - background: var(--ag-bg-surface); + border: 1px solid var(--ag-border); + border-radius: var(--ag-radius-sm); + background: var(--ag-surface-secondary); font-size: 0.75rem; font-weight: 500; } @@ -197,15 +277,15 @@ export const pluginFoundationCssText = ` .agw-panel { gap: 0; padding: 1.25rem; - background: var(--ag-bg-elevated); - border: 1px solid var(--ag-glass-border); - border-radius: var(--ag-radius-lg); + background: var(--ag-surface); + border: 1px solid var(--ag-border); + border-radius: var(--ag-radius-sm); } .agw-card-active { border-color: var(--ag-border-focus); - background: var(--ag-bg-surface); - box-shadow: 0 0 0 1px var(--ag-primary-subtle); + background: var(--ag-primary-subtle); + box-shadow: 0 0 0 1px color-mix(in oklab, var(--ag-primary) 22%, transparent); } .agw-selectable-card { @@ -224,6 +304,7 @@ export const pluginFoundationCssText = ` .agw-focus-ring:focus-visible { outline: 1.5px solid var(--ag-primary); outline-offset: 2px; + box-shadow: 0 0 0 2px color-mix(in oklab, var(--ag-primary) 18%, transparent); } .agw-button-primary, @@ -233,7 +314,7 @@ export const pluginFoundationCssText = ` align-items: center; justify-content: center; gap: 0.5rem; - border-radius: var(--ag-radius-md); + border-radius: var(--ag-radius-sm); padding: 0.5rem 1rem; font-size: 0.875rem; font-weight: 500; @@ -244,19 +325,18 @@ export const pluginFoundationCssText = ` .agw-button-primary { border: 1px solid transparent; background: var(--ag-primary); - color: var(--ag-text-inverse); - box-shadow: var(--ag-shadow-md); + color: var(--ag-primary-foreground); + box-shadow: none; } .agw-button-primary:hover { background: var(--ag-primary-hover); - box-shadow: var(--ag-shadow-lg); } .agw-button-secondary { - border: 1px solid var(--ag-glass-border); - background: var(--ag-bg-surface); - color: var(--ag-text); + border: 1px solid var(--ag-border); + background: var(--ag-default-bg); + color: var(--ag-default-foreground); } .agw-button-secondary:hover { @@ -284,16 +364,16 @@ export const pluginFoundationCssText = ` .agw-badge { display: inline-flex; align-items: center; - border-radius: 999px; + border-radius: var(--ag-radius-sm); padding: 0.25rem 0.625rem; font-size: 0.6875rem; font-weight: 500; - letter-spacing: 0.01em; + letter-spacing: 0; } .agw-badge-neutral { - background: var(--ag-glass); - color: var(--ag-text-secondary); + background: var(--ag-default-bg); + color: var(--ag-default-foreground); } .agw-badge-success { @@ -317,121 +397,48 @@ export const pluginFoundationCssText = ` .agw-input:disabled, .agw-selectable-card:disabled { cursor: not-allowed; - opacity: 0.5; + opacity: 0.45; } -/* ── 亮色主题:磨砂玻璃质感 ── */ - -[data-theme="light"] .agw-input { - background: rgba(255, 255, 255, 0.35); - border-color: rgba(180, 195, 220, 0.30); - box-shadow: 0 1px 2px rgba(100, 116, 160, 0.05) inset; - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); -} - -[data-theme="light"] .agw-input:focus, -[data-theme="light"] .agw-input:focus-visible { - background: rgba(255, 255, 255, 0.55); -} +/* ── Light theme and modal elevation follow HeroUI bridge tokens. ── */ [data-theme="light"] .agw-card, [data-theme="light"] .agw-panel { - background: rgba(255, 255, 255, 0.30); - border-color: rgba(180, 195, 220, 0.25); - box-shadow: - 0 2px 24px rgba(100, 116, 160, 0.07), - 0 0.5px 0 rgba(255, 255, 255, 0.55) inset; - backdrop-filter: blur(28px) saturate(1.5); - -webkit-backdrop-filter: blur(28px) saturate(1.5); -} - -[data-theme="light"] .agw-card:hover, -[data-theme="light"] .agw-selectable-card:hover { - background: rgba(255, 255, 255, 0.42); - border-color: rgba(180, 195, 220, 0.35); -} - -[data-theme="light"] .agw-status-inline { - background: rgba(255, 255, 255, 0.35); - border-color: rgba(180, 195, 220, 0.25); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); -} - -[data-theme="light"] .agw-button-secondary { - background: rgba(255, 255, 255, 0.38); - border-color: rgba(180, 195, 220, 0.30); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); + box-shadow: var(--ag-shadow-sm); } -[data-theme="light"] .agw-button-secondary:hover { - background: rgba(255, 255, 255, 0.55); - border-color: rgba(180, 195, 220, 0.40); +.ag-elevation-modal .agw-input { + background: var(--ag-field-background); + border-color: color-mix(in oklab, var(--ag-border) 88%, transparent); + box-shadow: var(--ag-shadow-sm); } -/* ── Elevation: modal — 弹窗内撤销 glass morphism ── - * 弹窗背景不透明,glass 效果(backdrop-filter, 半透明 background, 投影)既无意义又浪费 GPU。 - * 上面的基础规则使用硬编码 rgba/blur 值,无法通过 .ag-elevation-modal 的变量重定义级联覆盖, - * 因此这里需要显式: - * 1. backdrop-filter: none — 撤销模糊 - * 2. box-shadow: none — 去掉多余投影 - * 3. background / border-color 改为 var(--ag-*) 引用 — 让 elevation context 变量生效 - */ - -[data-theme="light"] .ag-elevation-modal .agw-input { - background: var(--ag-bg-surface); +.ag-elevation-modal .agw-card, +.ag-elevation-modal .agw-panel { + background: var(--ag-surface); border-color: var(--ag-border); - box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.04); - backdrop-filter: none; - -webkit-backdrop-filter: none; -} - -[data-theme="light"] .ag-elevation-modal .agw-input:focus, -[data-theme="light"] .ag-elevation-modal .agw-input:focus-visible { - background: rgba(0, 0, 0, 0.02); - border-color: var(--ag-border-focus); - box-shadow: 0 0 0 3px var(--ag-primary-subtle); -} - -[data-theme="light"] .ag-elevation-modal .agw-card, -[data-theme="light"] .ag-elevation-modal .agw-panel { - background: var(--ag-bg-elevated); - border-color: var(--ag-glass-border); box-shadow: none; - backdrop-filter: none; - -webkit-backdrop-filter: none; } -[data-theme="light"] .ag-elevation-modal .agw-card:hover, -[data-theme="light"] .ag-elevation-modal .agw-selectable-card:hover { - background: var(--ag-bg-hover); +.ag-elevation-modal .agw-card:hover, +.ag-elevation-modal .agw-selectable-card:hover { + background: var(--ag-surface-secondary); border-color: var(--ag-border); } -[data-theme="light"] .ag-elevation-modal .agw-card-active { +.ag-elevation-modal .agw-card-active { background: var(--ag-primary-subtle); border-color: var(--ag-border-focus); } -[data-theme="light"] .ag-elevation-modal .agw-button-secondary { - background: rgba(255, 255, 255, 0.50); - border-color: var(--ag-glass-border); - backdrop-filter: none; - -webkit-backdrop-filter: none; -} - -[data-theme="light"] .ag-elevation-modal .agw-button-secondary:hover { - background: rgba(255, 255, 255, 0.70); +.ag-elevation-modal .agw-button-secondary { + background: var(--ag-default-bg); border-color: var(--ag-border); } -[data-theme="light"] .ag-elevation-modal .agw-status-inline { - background: rgba(255, 255, 255, 0.40); - border-color: var(--ag-glass-border); - backdrop-filter: none; - -webkit-backdrop-filter: none; +.ag-elevation-modal .agw-button-secondary:hover { + background: var(--ag-bg-hover); + border-color: var(--ag-border); } `; diff --git a/frontend/src/tokens.ts b/frontend/src/tokens.ts index 1d5f3a5..240d998 100644 --- a/frontend/src/tokens.ts +++ b/frontend/src/tokens.ts @@ -8,103 +8,141 @@ import type { ThemeTokens, } from './types.js'; -/** 暗色主题 — Deep Ocean */ +/** 暗色主题 — HeroUI Theme Builder preset */ export const darkTheme: ThemeTokens = { - primary: '#3ecfb4', - primaryHover: '#62dcc4', - primarySubtle: 'rgba(62, 207, 180, 0.08)', - primaryGlow: 'rgba(62, 207, 180, 0.14)', - - success: '#34d399', - successSubtle: 'rgba(52, 211, 153, 0.12)', - warning: '#fbbf24', - warningSubtle: 'rgba(251, 191, 36, 0.12)', - danger: '#fb7185', - dangerSubtle: 'rgba(251, 113, 133, 0.12)', - info: '#7dd3fc', - infoSubtle: 'rgba(125, 211, 252, 0.12)', - - // 背景:深蓝黑,带微蓝底调增加深度感 - bgDeep: '#06080e', - bg: '#0c0f17', - bgElevated: '#131722', - bgSurface: '#1a1e2a', - bgHover: '#232836', - bgActive: '#2c3240', - - // 边框:蓝调透明 - border: 'rgba(148, 175, 225, 0.08)', - borderSubtle: 'rgba(148, 175, 225, 0.05)', - borderFocus: 'rgba(62, 207, 180, 0.40)', - - // 文字:微蓝白,长时间阅读更舒适 - text: '#e2e6f0', - textSecondary: '#8d93a8', - textTertiary: '#565d73', - textInverse: '#06080e', - - glass: 'rgba(148, 175, 225, 0.03)', - glassBorder: 'rgba(148, 175, 225, 0.06)', - - shadowSm: '0 2px 8px rgba(0, 0, 0, 0.36)', - shadowMd: '0 8px 24px rgba(0, 0, 0, 0.48)', - shadowLg: '0 20px 48px rgba(0, 0, 0, 0.60)', - shadowGlow: '0 0 0 1px rgba(62, 207, 180, 0.08), 0 8px 32px rgba(62, 207, 180, 0.10)', + primary: 'oklch(0.9848 0 0)', + primaryForeground: 'oklch(15% 0.0000 0.00)', + primaryHover: 'color-mix(in oklab, oklch(0.9848 0 0) 88%, oklch(15% 0.0000 0.00) 12%)', + primarySubtle: 'color-mix(in oklab, oklch(0.9848 0 0) 14%, transparent)', + primaryGlow: 'color-mix(in oklab, oklch(0.9848 0 0) 22%, transparent)', + + success: 'oklch(73.29% 0.1935 120.35)', + successForeground: 'oklch(21.03% 0.0059 120.35)', + successSubtle: 'color-mix(in oklab, oklch(73.29% 0.1935 120.35) 15%, transparent)', + warning: 'oklch(0.8803 0.1348 86.06)', + warningForeground: 'oklch(15% 0.0404 86.06)', + warningSubtle: 'color-mix(in oklab, oklch(0.8803 0.1348 86.06) 15%, transparent)', + danger: 'oklch(0.7044 0.1872 23.19)', + dangerForeground: 'oklch(15% 0.0500 23.19)', + dangerSubtle: 'color-mix(in oklab, oklch(0.7044 0.1872 23.19) 15%, transparent)', + info: 'oklch(0.9848 0 0)', + infoSubtle: 'color-mix(in oklab, oklch(0.9848 0 0) 14%, transparent)', + + defaultBg: 'oklch(27.40% 0.0000 0.00)', + defaultForeground: 'oklch(99.11% 0 0)', + fieldBackground: 'oklch(21.03% 0.0000 0.00)', + fieldForeground: 'oklch(99.11% 0.0000 0.00)', + fieldPlaceholder: 'oklch(70.50% 0.0000 0.00)', + muted: 'oklch(70.50% 0.0000 0.00)', + overlay: 'oklch(21.03% 0.0000 0.00)', + overlayForeground: 'oklch(99.11% 0.0000 0.00)', + scrollbar: 'oklch(70.50% 0.0000 0.00)', + segment: 'oklch(39.64% 0.0000 0.00)', + segmentForeground: 'oklch(99.11% 0.0000 0.00)', + surface: 'oklch(21.03% 0.0000 0.00)', + surfaceForeground: 'oklch(99.11% 0.0000 0.00)', + surfaceSecondary: 'oklch(25.70% 0.0000 0.00)', + surfaceSecondaryForeground: 'oklch(99.11% 0.0000 0.00)', + surfaceTertiary: 'oklch(27.21% 0.0000 0.00)', + surfaceTertiaryForeground: 'oklch(99.11% 0.0000 0.00)', + + bgDeep: 'oklch(12.00% 0.0000 0.00)', + bg: 'oklch(12.00% 0.0000 0.00)', + bgElevated: 'oklch(21.03% 0.0000 0.00)', + bgSurface: 'oklch(21.03% 0.0000 0.00)', + bgHover: 'oklch(25.70% 0.0000 0.00)', + bgActive: 'oklch(27.21% 0.0000 0.00)', + + border: 'oklch(28.00% 0.0000 0.00)', + borderSubtle: 'oklch(25.00% 0.0000 0.00)', + borderFocus: 'oklch(0.9848 0 0)', + + text: 'oklch(99.11% 0.0000 0.00)', + textSecondary: 'oklch(70.50% 0.0000 0.00)', + textTertiary: 'oklch(70.50% 0.0000 0.00)', + textInverse: 'oklch(15% 0.0000 0.00)', + + glass: 'color-mix(in oklab, oklch(21.03% 0.0000 0.00) 92%, transparent)', + glassBorder: 'oklch(28.00% 0.0000 0.00)', + + shadowSm: '0 0 0 0 transparent inset', + shadowMd: '0 0 0 0 transparent inset', + shadowLg: '0 0 1px 0 #ffffff4d inset', + shadowGlow: '0 0 0 1px color-mix(in oklab, oklch(0.9848 0 0) 18%, transparent)', }; -/** 亮色主题 — Deep Ocean Light */ +/** 亮色主题 — HeroUI Theme Builder preset */ export const lightTheme: ThemeTokens = { - primary: '#0d9488', - primaryHover: '#0b7e74', - primarySubtle: 'rgba(13, 148, 136, 0.05)', - primaryGlow: 'rgba(13, 148, 136, 0.10)', - - success: '#16a34a', - successSubtle: 'rgba(22, 163, 74, 0.06)', - warning: '#d97706', - warningSubtle: 'rgba(217, 119, 6, 0.06)', - danger: '#e11d48', - dangerSubtle: 'rgba(225, 29, 72, 0.06)', - info: '#2563eb', - infoSubtle: 'rgba(37, 99, 235, 0.06)', - - // 背景:冷蓝灰,与暗色主题色温统一 - bgDeep: '#f1f3f8', - bg: '#f6f7fb', - bgElevated: '#ffffff', - bgSurface: '#ffffff', - bgHover: '#eaedf5', - bgActive: '#e0e4ee', - - // 边框:冷蓝调 - border: '#d6dae6', - borderSubtle: '#e6e9f2', - borderFocus: 'rgba(13, 148, 136, 0.45)', - - // 文字:深蓝黑,非纯黑,阅读更柔和 - text: '#131830', - textSecondary: '#424866', - textTertiary: '#6e7490', - textInverse: '#ffffff', - - glass: 'rgba(255, 255, 255, 0.92)', - glassBorder: 'rgba(10, 20, 60, 0.06)', - - // 阴影:带蓝调,更有层次 - shadowSm: '0 1px 3px rgba(10, 20, 60, 0.06), 0 1px 2px rgba(10, 20, 60, 0.04)', - shadowMd: '0 4px 12px rgba(10, 20, 60, 0.07), 0 2px 4px rgba(10, 20, 60, 0.04)', - shadowLg: '0 16px 40px rgba(10, 20, 60, 0.09), 0 4px 8px rgba(10, 20, 60, 0.04)', - shadowGlow: '0 0 0 1px rgba(13, 148, 136, 0.10), 0 8px 24px rgba(13, 148, 136, 0.07)', + primary: 'oklch(0 0 0)', + primaryForeground: 'oklch(99.11% 0 0)', + primaryHover: 'color-mix(in oklab, oklch(0 0 0) 88%, oklch(99.11% 0 0) 12%)', + primarySubtle: 'color-mix(in oklab, oklch(0 0 0) 10%, transparent)', + primaryGlow: 'color-mix(in oklab, oklch(0 0 0) 16%, transparent)', + + success: 'oklch(73.29% 0.1935 120.35)', + successForeground: 'oklch(21.03% 0.0059 120.35)', + successSubtle: 'color-mix(in oklab, oklch(73.29% 0.1935 120.35) 15%, transparent)', + warning: 'oklch(0.8446 0.1525 80.6)', + warningForeground: 'oklch(15% 0.0457 80.60)', + warningSubtle: 'color-mix(in oklab, oklch(0.8446 0.1525 80.6) 15%, transparent)', + danger: 'oklch(0.573 0.2249 21.97)', + dangerForeground: 'oklch(98% 0.0200 21.97)', + dangerSubtle: 'color-mix(in oklab, oklch(0.573 0.2249 21.97) 15%, transparent)', + info: 'oklch(0 0 0)', + infoSubtle: 'color-mix(in oklab, oklch(0 0 0) 10%, transparent)', + + defaultBg: 'oklch(94.00% 0.0000 0.00)', + defaultForeground: 'oklch(21.03% 0.0059 0.00)', + fieldBackground: 'oklch(100.00% 0.0000 0.00)', + fieldForeground: 'oklch(21.03% 0.0000 0.00)', + fieldPlaceholder: 'oklch(55.17% 0.0000 0.00)', + muted: 'oklch(55.17% 0.0000 0.00)', + overlay: 'oklch(100.00% 0.0000 0.00)', + overlayForeground: 'oklch(21.03% 0.0000 0.00)', + scrollbar: 'oklch(87.10% 0.0000 0.00)', + segment: 'oklch(100.00% 0.0000 0.00)', + segmentForeground: 'oklch(21.03% 0.0000 0.00)', + surface: 'oklch(100.00% 0.0000 0.00)', + surfaceForeground: 'oklch(21.03% 0.0000 0.00)', + surfaceSecondary: 'oklch(95.24% 0.0000 0.00)', + surfaceSecondaryForeground: 'oklch(21.03% 0.0000 0.00)', + surfaceTertiary: 'oklch(93.73% 0.0000 0.00)', + surfaceTertiaryForeground: 'oklch(21.03% 0.0000 0.00)', + + bgDeep: 'oklch(97.02% 0.0000 0.00)', + bg: 'oklch(97.02% 0.0000 0.00)', + bgElevated: 'oklch(100.00% 0.0000 0.00)', + bgSurface: 'oklch(100.00% 0.0000 0.00)', + bgHover: 'oklch(95.24% 0.0000 0.00)', + bgActive: 'oklch(93.73% 0.0000 0.00)', + + border: 'oklch(90.00% 0.0000 0.00)', + borderSubtle: 'oklch(92.00% 0.0000 0.00)', + borderFocus: 'oklch(0 0 0)', + + text: 'oklch(21.03% 0.0000 0.00)', + textSecondary: 'oklch(55.17% 0.0000 0.00)', + textTertiary: 'oklch(55.17% 0.0000 0.00)', + textInverse: 'oklch(99.11% 0 0)', + + glass: 'color-mix(in oklab, oklch(100.00% 0.0000 0.00) 92%, transparent)', + glassBorder: 'oklch(90.00% 0.0000 0.00)', + + shadowSm: '0 2px 4px 0 #0000000a, 0 1px 2px 0 #0000000f, 0 0 1px 0 #0000000f', + shadowMd: '0 2px 4px 0 #0000000a, 0 1px 2px 0 #0000000f, 0 0 1px 0 #0000000f', + shadowLg: '0 2px 8px 0 #0000000f, 0 -6px 12px 0 #00000008, 0 14px 28px 0 #00000014', + shadowGlow: '0 0 0 1px color-mix(in oklab, oklch(0 0 0) 12%, transparent)', }; -/** 通用基础 token */ +/** 通用基础 token:HeroUI Radius 为 Small,Radius Form 为 Medium。 */ export const foundationTokens: FoundationTokens = { - radiusSm: '12px', - radiusMd: '18px', - radiusLg: '22px', - radiusXl: '28px', - fontSans: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif", - fontMono: "'JetBrains Mono', 'SF Mono', 'Cascadia Code', monospace", + radiusSm: '0.25rem', + radiusMd: '0.25rem', + radiusLg: '0.25rem', + radiusXl: '0.25rem', + fieldRadius: '0.5rem', + fontSans: "'Geist Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif", + fontMono: "'Geist Mono', 'SF Mono', 'Cascadia Code', monospace", transition: '200ms cubic-bezier(0.4, 0, 0.2, 1)', transitionSlow: '400ms cubic-bezier(0.4, 0, 0.2, 1)', }; @@ -140,7 +178,7 @@ export const decorativePalette = [ '#84cc16', // lime '#f97316', // orange '#6366f1', // indigo - '#0d9488', // teal (primary) + '#0d9488', // teal '#a855f7', // purple ] as const; @@ -157,16 +195,10 @@ export const themes: Record = { */ export const lightElevationContexts: Record> = { modal: { - bgElevated: '#eef0f7', - bgSurface: '#e6e9f2', - bgHover: '#e0e4ee', - glassBorder: '#d2d6e3', - border: '#c8cdd9', - shadowSm: 'none', - shadowMd: 'none', + // HeroUI preset already provides overlay/surface tokens for modal elevation. + // Keep this empty unless a plugin foundation rule needs a scoped correction. }, dropdown: { - // dropdown 背景由宿主的 .ag-glass-dropdown 容器类处理 - // 预留空位,未来可扩展 + // HeroUI dropdown/tooltip surfaces are now handled by the host bridge. }, }; diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 9306d2e..47e8c1c 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -2,20 +2,43 @@ export interface ThemeTokens { // 主色调 primary: string; + primaryForeground: string; primaryHover: string; primarySubtle: string; primaryGlow: string; // 语义色 success: string; + successForeground: string; successSubtle: string; warning: string; + warningForeground: string; warningSubtle: string; danger: string; + dangerForeground: string; dangerSubtle: string; info: string; infoSubtle: string; + // HeroUI 语义表面 + defaultBg: string; + defaultForeground: string; + fieldBackground: string; + fieldForeground: string; + fieldPlaceholder: string; + muted: string; + overlay: string; + overlayForeground: string; + scrollbar: string; + segment: string; + segmentForeground: string; + surface: string; + surfaceForeground: string; + surfaceSecondary: string; + surfaceSecondaryForeground: string; + surfaceTertiary: string; + surfaceTertiaryForeground: string; + // 背景层次 bgDeep: string; bg: string; @@ -52,6 +75,7 @@ export interface FoundationTokens { radiusMd: string; radiusLg: string; radiusXl: string; + fieldRadius: string; fontSans: string; fontMono: string; transition: string;