openpencil/packages/core/src/constants.ts
Danila Poyarkov b3cbf227e6
Fix per-character text fill colors and vector region loop direction (#95)
* Fix per-character text fill colors and vector region loop direction

* Replace rotation handle with corner rotation zones

Remove the rotation handle circle/stem above the selection bounding box.
Rotation is now triggered from corner zones outside resize handles,
matching Figma's behavior. Cursor uses native CSS directional arrows
(n-resize, ne-resize, etc.) that rotate with the node, avoiding
cross-platform SVG cursor issues.
2026-03-12 18:28:04 +03:00

262 lines
8.2 KiB
TypeScript

import type { Color } from './types'
export const IS_TAURI = typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window
export const BLACK: Color = { r: 0, g: 0, b: 0, a: 1 }
export const TRANSPARENT: Color = { r: 0, g: 0, b: 0, a: 0 }
export const DEFAULT_SHADOW_COLOR: Color = { r: 0, g: 0, b: 0, a: 0.25 }
export const SELECTION_COLOR = { r: 0.23, g: 0.51, b: 0.96, a: 1 } satisfies Color
export const COMPONENT_COLOR = { r: 0.592, g: 0.278, b: 1, a: 1 } satisfies Color
export const SNAP_COLOR = { r: 1.0, g: 0.0, b: 0.56, a: 1 } satisfies Color
export const CANVAS_BG_COLOR = { r: 0.96, g: 0.96, b: 0.96, a: 1 } satisfies Color
export const SNAP_THRESHOLD = 5
export const RULER_SIZE = 20
export const RULER_BG_COLOR = { r: 0.14, g: 0.14, b: 0.14, a: 1 } satisfies Color
export const RULER_TICK_COLOR = { r: 0.4, g: 0.4, b: 0.4, a: 1 } satisfies Color
export const RULER_TEXT_COLOR = { r: 0.55, g: 0.55, b: 0.55, a: 1 } satisfies Color
export const RULER_BADGE_HEIGHT = 14
export const RULER_BADGE_PADDING = 3
export const RULER_BADGE_RADIUS = 2
export const RULER_BADGE_EXCLUSION = 30
export const RULER_TEXT_BASELINE = 0.65
export const RULER_MAJOR_TICK = 0.5
export const RULER_MINOR_TICK = 0.25
export const RULER_HIGHLIGHT_ALPHA = 0.3
export const PEN_HANDLE_RADIUS = 3
export const PEN_VERTEX_RADIUS = 4
export const PEN_CLOSE_RADIUS_BOOST = 2
export const PEN_PATH_STROKE_WIDTH = 2
export const PARENT_OUTLINE_ALPHA = 0.5
export const PARENT_OUTLINE_DASH = 4
export const DEFAULT_FONT_FAMILY = 'Inter'
export const DEFAULT_FONT_SIZE = 14
export const DEFAULT_STROKE_MITER_LIMIT = 4
export const LABEL_FONT_SIZE = 11
export const SIZE_FONT_SIZE = 10
export const HANDLE_HALF_SIZE = 3
export const LABEL_OFFSET_Y = 8
export const SIZE_PILL_PADDING_X = 6
export const SIZE_PILL_PADDING_Y = 6
export const SIZE_PILL_HEIGHT = 18
export const SIZE_PILL_RADIUS = 4
export const SIZE_PILL_TEXT_OFFSET_Y = 13
export const MARQUEE_FILL_ALPHA = 0.08
export const SELECTION_DASH_ALPHA = 0.6
export const DROP_HIGHLIGHT_ALPHA = 0.8
export const DROP_HIGHLIGHT_STROKE = 2
export const LAYOUT_INDICATOR_STROKE = 2
export const SECTION_CORNER_RADIUS = 5
export const SECTION_TITLE_HEIGHT = 24
export const SECTION_TITLE_PADDING_X = 8
export const SECTION_TITLE_RADIUS = 5
export const SECTION_TITLE_FONT_SIZE = 12
export const SECTION_TITLE_GAP = 6
export const COMPONENT_SET_DASH = 6
export const COMPONENT_SET_DASH_GAP = 4
export const COMPONENT_SET_BORDER_WIDTH = 1.5
export const COMPONENT_LABEL_FONT_SIZE = 11
export const COMPONENT_LABEL_GAP = 6
export const COMPONENT_LABEL_ICON_SIZE = 10
export const COMPONENT_LABEL_ICON_GAP = 4
export const RULER_TARGET_PIXEL_SPACING = 100
export const RULER_MAJOR_TOLERANCE = 0.01
export const FLASH_COLOR = SELECTION_COLOR
export const FLASH_ATTACK_MS = 200
export const FLASH_HOLD_MS = 400
export const FLASH_RELEASE_MS = 300
export const FLASH_STROKE_WIDTH = 2
export const FLASH_PADDING = 5
export const FLASH_OVERSHOOT = 30
export const FLASH_RADIUS = 4
export const TEXT_SELECTION_COLOR = { r: 0.26, g: 0.52, b: 0.96, a: 0.3 }
export const TEXT_CARET_COLOR = BLACK
export const TEXT_CARET_WIDTH = 1
export type AIProviderID =
| 'openrouter'
| 'anthropic'
| 'openai'
| 'google'
| 'openai-compatible'
| 'zai'
| 'minimax'
| 'anthropic-compatible'
export interface ModelOption {
id: string
name: string
tag?: string
}
export interface AIProviderDef {
id: AIProviderID
name: string
keyPlaceholder: string
keyURL: string
models: ModelOption[]
defaultModel: string
supportsCustomBaseURL?: boolean
supportsCustomModel?: boolean
}
export const AI_PROVIDERS: AIProviderDef[] = [
{
id: 'openrouter',
name: 'OpenRouter',
keyPlaceholder: 'sk-or-…',
keyURL: 'https://openrouter.ai/keys',
defaultModel: 'anthropic/claude-sonnet-4.6',
models: [
{ id: 'anthropic/claude-sonnet-4.6', name: 'Claude Sonnet 4.6', tag: 'Best for design' },
{ id: 'anthropic/claude-opus-4.6', name: 'Claude Opus 4.6', tag: 'Smartest' },
{ id: 'moonshotai/kimi-k2.5', name: 'Kimi K2.5', tag: 'Vision + code' },
{
id: 'google/gemini-3.1-pro-preview',
name: 'Gemini 3.1 Pro',
tag: '1M context'
},
{ id: 'openai/gpt-5.3-codex', name: 'GPT-5.3 Codex' },
{ id: 'google/gemini-3-flash-preview', name: 'Gemini 3 Flash', tag: 'Fast' },
{ id: 'deepseek/deepseek-v3.2', name: 'DeepSeek V3.2', tag: 'Cheap' },
{ id: 'qwen/qwen3.5-flash-02-23', name: 'Qwen 3.5 Flash', tag: 'Cheap' },
{ id: 'qwen/qwen3-coder:free', name: 'Qwen3 Coder', tag: 'Free' },
{ id: 'openai/gpt-oss-120b:free', name: 'GPT-OSS 120B', tag: 'Free' }
]
},
{
id: 'anthropic',
name: 'Anthropic',
keyPlaceholder: 'sk-ant-…',
keyURL: 'https://console.anthropic.com/settings/keys',
defaultModel: 'claude-sonnet-4-6-20260301',
models: [
{ id: 'claude-sonnet-4-6-20260301', name: 'Claude Sonnet 4.6', tag: 'Best for design' },
{ id: 'claude-opus-4-6-20260301', name: 'Claude Opus 4.6', tag: 'Smartest' }
]
},
{
id: 'openai',
name: 'OpenAI',
keyPlaceholder: 'sk-…',
keyURL: 'https://platform.openai.com/api-keys',
defaultModel: 'gpt-5.3-codex',
models: [
{ id: 'gpt-5.3-codex', name: 'GPT-5.3 Codex' },
{ id: 'gpt-4.1', name: 'GPT-4.1' },
{ id: 'o3', name: 'o3', tag: 'Reasoning' },
{ id: 'o4-mini', name: 'o4-mini', tag: 'Fast reasoning' }
]
},
{
id: 'google',
name: 'Google AI',
keyPlaceholder: 'AIza…',
keyURL: 'https://aistudio.google.com/apikey',
defaultModel: 'gemini-3.1-pro-preview',
models: [
{ id: 'gemini-3.1-pro-preview', name: 'Gemini 3.1 Pro', tag: '1M context' },
{ id: 'gemini-3-flash-preview', name: 'Gemini 3 Flash', tag: 'Fast' }
]
},
{
id: 'zai',
name: 'Z.ai',
keyPlaceholder: 'API key',
keyURL: 'https://docs.z.ai/api-reference/introduction',
defaultModel: 'glm-5',
models: [
{ id: 'glm-5', name: 'GLM-5' },
{ id: 'glm-5-code', name: 'GLM-5-Code' },
{ id: 'glm-4.7', name: 'GLM-4.7' },
{ id: 'glm-4.7-flashx', name: 'GLM-4.7-FlashX' },
{ id: 'glm-4.6', name: 'GLM-4.6' },
{ id: 'glm-4.5', name: 'GLM-4.5' },
{ id: 'glm-4.5-x', name: 'GLM-4.5-X' },
{ id: 'glm-4.5-air', name: 'GLM-4.5-Air' },
{ id: 'glm-4.5-airx', name: 'GLM-4.5-AirX' },
{ id: 'glm-4-32b-0414-128k', name: 'GLM-4-32B-0414-128K' },
{ id: 'glm-4.7-flash', name: 'GLM-4.7-Flash', tag: 'Free' },
{ id: 'glm-4.5-flash', name: 'GLM-4.5-Flash', tag: 'Free' }
]
},
{
id: 'minimax',
name: 'MiniMax',
keyPlaceholder: 'API key',
keyURL: 'https://platform.minimax.io/user-center/basic-information/interface-key',
defaultModel: 'MiniMax-M2.5',
models: [
{ id: 'MiniMax-M2.5', name: 'MiniMax-M2.5', tag: 'Best' },
{ id: 'MiniMax-M2.5-highspeed', name: 'MiniMax-M2.5 Highspeed', tag: 'Fast' },
{ id: 'MiniMax-M2.1', name: 'MiniMax-M2.1' },
{ id: 'MiniMax-M2.1-highspeed', name: 'MiniMax-M2.1 Highspeed', tag: 'Fast' },
{ id: 'MiniMax-M2', name: 'MiniMax-M2' }
]
},
{
id: 'openai-compatible',
name: 'OpenAI-compatible',
keyPlaceholder: 'API key',
keyURL: '',
defaultModel: '',
models: [],
supportsCustomBaseURL: true,
supportsCustomModel: true
},
{
id: 'anthropic-compatible',
name: 'Anthropic-compatible',
keyPlaceholder: 'API key',
keyURL: '',
defaultModel: '',
models: [],
supportsCustomBaseURL: true,
supportsCustomModel: true
}
]
export const DEFAULT_AI_PROVIDER: AIProviderID = 'openrouter'
export const DEFAULT_AI_MODEL = AI_PROVIDERS[0].defaultModel
export const AUTOMATION_HTTP_PORT = 7600
export const AUTOMATION_WS_PORT = 7601
export const GOOGLE_FONTS_API_KEY = 'AIzaSyD1tYDR_dUEiV-Tw1vksEhZbUytgKW5pc8'
export const CJK_FALLBACK_FAMILIES_MACOS = [
'PingFang SC',
'Hiragino Sans',
'Apple SD Gothic Neo',
'Heiti SC'
]
export const CJK_FALLBACK_FAMILIES_WINDOWS = [
'Microsoft YaHei',
'Microsoft JhengHei',
'Yu Gothic',
'Malgun Gothic',
'SimHei'
]
export const CJK_FALLBACK_FAMILIES_LINUX = [
'Noto Sans CJK SC',
'Noto Sans CJK JP',
'Noto Sans CJK KR',
'WenQuanYi Micro Hei',
'Droid Sans Fallback'
]
export const CJK_GOOGLE_FONT = 'Noto Sans SC'