@krometra/ui-kit 0.0.0-stage → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +235 -2
  3. package/dist/ui-kit.css +2 -0
  4. package/dist/ui-kit.js +15222 -0
  5. package/dist/ui-kit.umd.cjs +3 -0
  6. package/package.json +74 -3
  7. package/src/base.css +32 -0
  8. package/src/components/KAlert.vue +65 -0
  9. package/src/components/KAvatar.vue +67 -0
  10. package/src/components/KBadge.vue +47 -0
  11. package/src/components/KButton.vue +98 -0
  12. package/src/components/KCard.vue +63 -0
  13. package/src/components/KCheckbox.vue +105 -0
  14. package/src/components/KCombobox.vue +232 -0
  15. package/src/components/KCountdown.vue +74 -0
  16. package/src/components/KDescriptions.vue +40 -0
  17. package/src/components/KDropdown.vue +111 -0
  18. package/src/components/KEmpty.vue +39 -0
  19. package/src/components/KFileUpload.vue +121 -0
  20. package/src/components/KFilterBar.vue +91 -0
  21. package/src/components/KGantt.vue +398 -0
  22. package/src/components/KIcon.vue +53 -0
  23. package/src/components/KInput.vue +108 -0
  24. package/src/components/KMenuItem.vue +56 -0
  25. package/src/components/KModal.vue +136 -0
  26. package/src/components/KMoney.vue +44 -0
  27. package/src/components/KPage.vue +93 -0
  28. package/src/components/KPagination.vue +113 -0
  29. package/src/components/KProgress.vue +63 -0
  30. package/src/components/KRadioGroup.vue +103 -0
  31. package/src/components/KSegmented.vue +88 -0
  32. package/src/components/KSelect.vue +231 -0
  33. package/src/components/KShell.vue +176 -0
  34. package/src/components/KStat.vue +74 -0
  35. package/src/components/KSteps.vue +119 -0
  36. package/src/components/KSwitch.vue +82 -0
  37. package/src/components/KTable.vue +161 -0
  38. package/src/components/KTabs.vue +102 -0
  39. package/src/components/KTextarea.vue +79 -0
  40. package/src/components/KTimeline.vue +72 -0
  41. package/src/components/KToast.vue +34 -0
  42. package/src/components/KToastItem.vue +86 -0
  43. package/src/components/KTooltip.vue +67 -0
  44. package/src/components/icon-registry.js +143 -0
  45. package/src/composables/useToast.js +52 -0
  46. package/src/format.js +137 -0
  47. package/src/index.js +85 -0
  48. package/src/theme.js +417 -0
  49. package/src/tokens.css +109 -0
package/src/theme.js ADDED
@@ -0,0 +1,417 @@
1
+ /**
2
+ * Темы оформления.
3
+ *
4
+ * Кит намеренно не привязан к одному фирменному стилю: под каждый проект
5
+ * меняется палитра, скругления и плотность, а разметка и поведение компонентов
6
+ * остаются теми же. Тема — это набор значений для CSS-переменных `--k-*`,
7
+ * который применяется к любому элементу-контейнеру (обычно к :root).
8
+ *
9
+ * import { applyTheme, themes } from '@krometra/ui-kit'
10
+ * applyTheme('slate') // готовый пресет
11
+ * applyTheme({ preset: 'ocean', radius: 'sharp', density: 'compact' })
12
+ * applyTheme({ accent: '#7c3aed' }) // свой акцент
13
+ * applyTheme({ preset: 'scanflow', scheme: 'dark' }) // тёмная схема
14
+ * applyTheme({ scheme: 'system' }) // за системой
15
+ *
16
+ * Всё, что не задано, наследуется из tokens.css, поэтому частичная тема —
17
+ * законная и самая частая форма настройки.
18
+ */
19
+
20
+ export const schemes = ['light', 'dark', 'system']
21
+
22
+ /**
23
+ * Пресеты акцента и нейтралей.
24
+ *
25
+ * Нейтрали задаются отдельно от акцента: одинаковый синий на холодном и на
26
+ * тёплом сером выглядит как два разных бренда, и подбирать их порознь удобнее,
27
+ * чем держать десяток почти одинаковых палитр. Пресет может уточнить нейтрали
28
+ * точечно через `neutralOverrides` — так живут проектные темы.
29
+ */
30
+ export const presets = {
31
+ default: {
32
+ label: 'Default',
33
+ accent: '#2563eb',
34
+ accentHover: '#1d4ed8',
35
+ accentSoft: '#eff6ff',
36
+ neutral: 'cool',
37
+ },
38
+ scanflow: {
39
+ label: 'ScanFlow',
40
+ accent: '#2563eb',
41
+ accentHover: '#1d4ed8',
42
+ accentSoft: '#eff6ff',
43
+ neutral: 'cool',
44
+ neutralOverrides: {
45
+ light: { bg: '#f8fafc', surfaceMuted: '#f1f5f9', border: '#e2e8f0', text: '#1e293b' },
46
+ dark: { bg: '#0f172a', surface: '#1e293b', surfaceMuted: '#273549', border: '#334155', borderStrong: '#475569' },
47
+ },
48
+ },
49
+ slate: {
50
+ label: 'Slate',
51
+ accent: '#475569',
52
+ accentHover: '#334155',
53
+ accentSoft: '#f1f5f9',
54
+ neutral: 'cool',
55
+ },
56
+ ocean: {
57
+ label: 'Ocean',
58
+ accent: '#0891b2',
59
+ accentHover: '#0e7490',
60
+ accentSoft: '#ecfeff',
61
+ neutral: 'cool',
62
+ },
63
+ forest: {
64
+ label: 'Forest',
65
+ accent: '#059669',
66
+ accentHover: '#047857',
67
+ accentSoft: '#ecfdf5',
68
+ neutral: 'warm',
69
+ },
70
+ violet: {
71
+ label: 'Violet',
72
+ accent: '#7c3aed',
73
+ accentHover: '#6d28d9',
74
+ accentSoft: '#f5f3ff',
75
+ neutral: 'cool',
76
+ },
77
+ amber: {
78
+ label: 'Amber',
79
+ accent: '#d97706',
80
+ accentHover: '#b45309',
81
+ accentSoft: '#fffbeb',
82
+ neutral: 'warm',
83
+ },
84
+ rose: {
85
+ label: 'Rose',
86
+ accent: '#e11d48',
87
+ accentHover: '#be123c',
88
+ accentSoft: '#fff1f2',
89
+ neutral: 'warm',
90
+ },
91
+ mono: {
92
+ label: 'Mono',
93
+ accent: '#18181b',
94
+ accentHover: '#000000',
95
+ accentSoft: '#f4f4f5',
96
+ neutral: 'plain',
97
+ },
98
+ }
99
+
100
+ /** Нейтральные шкалы: фон, поверхности, границы, текст — для обеих схем. */
101
+ export const neutrals = {
102
+ cool: {
103
+ light: {
104
+ bg: '#f6f8fb',
105
+ surface: '#ffffff',
106
+ surfaceMuted: '#f8fafc',
107
+ border: '#e4e9f0',
108
+ borderStrong: '#cbd5e1',
109
+ text: '#172033',
110
+ textMuted: '#64748b',
111
+ textFaint: '#94a3b8',
112
+ },
113
+ dark: {
114
+ bg: '#0b1220',
115
+ surface: '#111a2b',
116
+ surfaceMuted: '#18233a',
117
+ border: '#24314a',
118
+ borderStrong: '#354764',
119
+ text: '#e5eaf3',
120
+ textMuted: '#9aa7bb',
121
+ textFaint: '#66748c',
122
+ },
123
+ },
124
+ warm: {
125
+ light: {
126
+ bg: '#faf9f7',
127
+ surface: '#ffffff',
128
+ surfaceMuted: '#f7f5f2',
129
+ border: '#ebe7e1',
130
+ borderStrong: '#d6cfc5',
131
+ text: '#26211c',
132
+ textMuted: '#6f665c',
133
+ textFaint: '#a39a8f',
134
+ },
135
+ dark: {
136
+ bg: '#15120f',
137
+ surface: '#1c1814',
138
+ surfaceMuted: '#24201b',
139
+ border: '#302a24',
140
+ borderStrong: '#443c34',
141
+ text: '#efe9e2',
142
+ textMuted: '#a89e92',
143
+ textFaint: '#776d62',
144
+ },
145
+ },
146
+ plain: {
147
+ light: {
148
+ bg: '#fafafa',
149
+ surface: '#ffffff',
150
+ surfaceMuted: '#f4f4f5',
151
+ border: '#e7e7e9',
152
+ borderStrong: '#d0d0d4',
153
+ text: '#18181b',
154
+ textMuted: '#6b7280',
155
+ textFaint: '#a1a1aa',
156
+ },
157
+ dark: {
158
+ bg: '#0f0f11',
159
+ surface: '#17171a',
160
+ surfaceMuted: '#1f1f23',
161
+ border: '#2a2a30',
162
+ borderStrong: '#3c3c44',
163
+ text: '#ededf0',
164
+ textMuted: '#a1a1aa',
165
+ textFaint: '#6b6b75',
166
+ },
167
+ },
168
+ }
169
+
170
+ /**
171
+ * Семантические цвета статусов. `strong` — текст на мягком фоне (бейджи,
172
+ * плашки), `soft` в тёмной схеме выводится смешиванием с поверхностью.
173
+ */
174
+ export const semantics = {
175
+ light: {
176
+ success: { base: '#059669', hover: '#047857', soft: '#ecfdf5', strong: '#065f46' },
177
+ warning: { base: '#f59e0b', hover: '#d97706', soft: '#fffbeb', strong: '#b45309' },
178
+ danger: { base: '#dc2626', hover: '#b91c1c', soft: '#fef2f2', strong: '#991b1b' },
179
+ info: { base: '#4f46e5', hover: '#4338ca', soft: '#eef2ff', strong: '#3730a3' },
180
+ },
181
+ dark: {
182
+ success: { base: '#34d399', hover: '#6ee7b7', strong: '#6ee7b7' },
183
+ warning: { base: '#fbbf24', hover: '#fcd34d', strong: '#fcd34d' },
184
+ danger: { base: '#f87171', hover: '#fca5a5', strong: '#fca5a5' },
185
+ info: { base: '#818cf8', hover: '#a5b4fc', strong: '#a5b4fc' },
186
+ },
187
+ }
188
+
189
+ /** Скругления: от строгих прямых углов до подчёркнуто мягких. */
190
+ export const radiusScales = {
191
+ sharp: { sm: '2px', md: '3px', lg: '5px' },
192
+ subtle: { sm: '4px', md: '5px', lg: '8px' },
193
+ default: { sm: '6px', md: '8px', lg: '14px' },
194
+ soft: { sm: '9px', md: '12px', lg: '20px' },
195
+ pill: { sm: '12px', md: '16px', lg: '28px' },
196
+ }
197
+
198
+ /** Плотность: высота контролов и базовый шаг. */
199
+ export const densities = {
200
+ compact: { controlH: '2.125rem', controlHSm: '1.75rem', gap: '0.375rem' },
201
+ default: { controlH: '2.5rem', controlHSm: '2rem', gap: '0.5rem' },
202
+ comfortable: { controlH: '2.875rem', controlHSm: '2.25rem', gap: '0.625rem' },
203
+ }
204
+
205
+ /** Тени: от плоского интерфейса до выраженной глубины. */
206
+ export const elevations = {
207
+ flat: {
208
+ light: { sm: 'none', md: '0 1px 2px rgba(15, 23, 42, 0.06)', lg: '0 6px 20px rgba(15, 23, 42, 0.10)' },
209
+ dark: { sm: 'none', md: '0 1px 2px rgba(0, 0, 0, 0.3)', lg: '0 6px 20px rgba(0, 0, 0, 0.45)' },
210
+ },
211
+ default: {
212
+ light: { sm: '0 1px 2px rgba(15, 23, 42, 0.05)', md: '0 4px 16px rgba(15, 23, 42, 0.08)', lg: '0 18px 44px rgba(15, 23, 42, 0.14)' },
213
+ dark: { sm: '0 1px 2px rgba(0, 0, 0, 0.35)', md: '0 4px 16px rgba(0, 0, 0, 0.45)', lg: '0 18px 44px rgba(0, 0, 0, 0.6)' },
214
+ },
215
+ lifted: {
216
+ light: { sm: '0 1px 3px rgba(15, 23, 42, 0.08)', md: '0 8px 24px rgba(15, 23, 42, 0.12)', lg: '0 24px 60px rgba(15, 23, 42, 0.20)' },
217
+ dark: { sm: '0 1px 3px rgba(0, 0, 0, 0.45)', md: '0 8px 24px rgba(0, 0, 0, 0.55)', lg: '0 24px 60px rgba(0, 0, 0, 0.7)' },
218
+ },
219
+ }
220
+
221
+ /** Шрифтовые пары. */
222
+ export const fontStacks = {
223
+ system: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif",
224
+ grotesk: "'Inter', 'Segoe UI', system-ui, sans-serif",
225
+ serif: "'Georgia', 'Times New Roman', serif",
226
+ mono: "ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace",
227
+ }
228
+
229
+ /** Сводит `system` к конкретной схеме по настройке ОС; вне браузера — light. */
230
+ export function resolveScheme(scheme = 'light') {
231
+ if (scheme !== 'system') return scheme === 'dark' ? 'dark' : 'light'
232
+ if (typeof window === 'undefined' || !window.matchMedia) return 'light'
233
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
234
+ }
235
+
236
+ /**
237
+ * Собирает карту CSS-переменных для темы.
238
+ *
239
+ * Возвращает простой объект, а не строку стилей: его одинаково удобно
240
+ * применить через element.style.setProperty и отрендерить в <style> при SSR.
241
+ */
242
+ export function buildTheme(theme = {}) {
243
+ const spec = typeof theme === 'string' ? { preset: theme } : theme
244
+ const scheme = resolveScheme(spec.scheme)
245
+ const dark = scheme === 'dark'
246
+ const preset = presets[spec.preset] ?? presets.default
247
+ const neutralFamily = neutrals[spec.neutral ?? preset.neutral] ?? neutrals.cool
248
+ const neutral = {
249
+ ...neutralFamily[scheme],
250
+ ...(spec.neutral ? {} : preset.neutralOverrides?.[scheme]),
251
+ ...(spec.bg ? { bg: spec.bg } : {}),
252
+ ...(spec.surface ? { surface: spec.surface } : {}),
253
+ ...(spec.text ? { text: spec.text } : {}),
254
+ ...(spec.textMuted ? { textMuted: spec.textMuted } : {}),
255
+ ...(spec.textFaint ? { textFaint: spec.textFaint } : {}),
256
+ }
257
+ const radius = radiusScales[spec.radius] ?? radiusScales.default
258
+ const density = densities[spec.density] ?? densities.default
259
+ const elevation = (elevations[spec.elevation] ?? elevations.default)[scheme]
260
+ const font = fontStacks[spec.font] ?? spec.font ?? fontStacks.system
261
+
262
+ const baseAccent = spec.accent ?? preset.accent
263
+ const accent = dark ? tint(baseAccent, 0.12) : baseAccent
264
+ const accentHover = spec.accentHover
265
+ ?? (dark ? tint(accent, 0.14) : spec.accent ? shade(accent, -0.12) : preset.accentHover)
266
+ const accentSoft = spec.accentSoft
267
+ ?? (dark ? mix(accent, neutral.surface, 0.22) : spec.accent ? tint(accent, 0.92) : preset.accentSoft)
268
+
269
+ const vars = {
270
+ '--k-primary': accent,
271
+ '--k-primary-hover': accentHover,
272
+ '--k-primary-soft': accentSoft,
273
+ '--k-on-primary': onColor(accent),
274
+
275
+ '--k-bg': neutral.bg,
276
+ '--k-surface': neutral.surface,
277
+ '--k-surface-muted': neutral.surfaceMuted,
278
+ '--k-border': neutral.border,
279
+ '--k-border-strong': neutral.borderStrong,
280
+ '--k-text': neutral.text,
281
+ '--k-text-muted': neutral.textMuted,
282
+ '--k-text-faint': neutral.textFaint,
283
+
284
+ '--k-overlay': dark ? 'rgba(0, 0, 0, 0.6)' : 'rgba(15, 23, 42, 0.35)',
285
+ '--k-tooltip-bg': dark ? neutral.text : '#172033',
286
+ '--k-tooltip-text': dark ? neutral.bg : '#ffffff',
287
+ '--k-selection': mix(accent, neutral.surface, dark ? 0.4 : 0.2),
288
+
289
+ '--k-radius-sm': radius.sm,
290
+ '--k-radius': radius.md,
291
+ '--k-radius-lg': radius.lg,
292
+
293
+ '--k-control-h': density.controlH,
294
+ '--k-control-h-sm': density.controlHSm,
295
+ '--k-gap': density.gap,
296
+
297
+ '--k-shadow-sm': elevation.sm,
298
+ '--k-shadow-md': elevation.md,
299
+ '--k-shadow-lg': elevation.lg,
300
+
301
+ '--k-font': font,
302
+ }
303
+
304
+ for (const [name, tone] of Object.entries(semantics[scheme])) {
305
+ vars[`--k-${name}`] = tone.base
306
+ vars[`--k-${name}-hover`] = tone.hover
307
+ vars[`--k-${name}-soft`] = tone.soft ?? mix(tone.base, neutral.surface, 0.18)
308
+ vars[`--k-${name}-strong`] = tone.strong
309
+ vars[`--k-on-${name}`] = onColor(tone.base)
310
+ }
311
+
312
+ return vars
313
+ }
314
+
315
+ /**
316
+ * Применяет тему к элементу (по умолчанию к :root).
317
+ *
318
+ * Помимо переменных выставляет `data-k-scheme` и `color-scheme`, чтобы
319
+ * нативные контролы и скроллбары следовали схеме. При `scheme: 'system'`
320
+ * подписывается на смену системной темы. Возвращает функцию отката — удобно
321
+ * для витрин и предпросмотра, где тема временная.
322
+ */
323
+ export function applyTheme(theme = {}, target = null) {
324
+ const el = target ?? (typeof document !== 'undefined' ? document.documentElement : null)
325
+ if (!el) return () => {}
326
+
327
+ const spec = typeof theme === 'string' ? { preset: theme } : theme
328
+ const previous = {}
329
+ const previousScheme = el.getAttribute('data-k-scheme')
330
+ const previousColorScheme = el.style.colorScheme
331
+
332
+ const paint = () => {
333
+ const vars = buildTheme(spec)
334
+ for (const [key, value] of Object.entries(vars)) {
335
+ if (!(key in previous)) previous[key] = el.style.getPropertyValue(key)
336
+ el.style.setProperty(key, value)
337
+ }
338
+ const scheme = resolveScheme(spec.scheme)
339
+ el.setAttribute('data-k-scheme', scheme)
340
+ el.style.colorScheme = scheme
341
+ }
342
+
343
+ paint()
344
+
345
+ let media = null
346
+ if (spec.scheme === 'system' && typeof window !== 'undefined' && window.matchMedia) {
347
+ media = window.matchMedia('(prefers-color-scheme: dark)')
348
+ media.addEventListener('change', paint)
349
+ }
350
+
351
+ return () => {
352
+ media?.removeEventListener('change', paint)
353
+ for (const [key, value] of Object.entries(previous)) {
354
+ if (value) el.style.setProperty(key, value)
355
+ else el.style.removeProperty(key)
356
+ }
357
+ if (previousScheme) el.setAttribute('data-k-scheme', previousScheme)
358
+ else el.removeAttribute('data-k-scheme')
359
+ el.style.colorScheme = previousColorScheme
360
+ }
361
+ }
362
+
363
+ /** Тема как строка для атрибута style или тега <style>. */
364
+ export function themeToCss(theme = {}, selector = ':root') {
365
+ const spec = typeof theme === 'string' ? { preset: theme } : theme
366
+ const lines = Object.entries(buildTheme(spec)).map(([k, v]) => ` ${k}: ${v};`)
367
+ lines.push(` color-scheme: ${resolveScheme(spec.scheme)};`)
368
+ return `${selector} {\n${lines.join('\n')}\n}`
369
+ }
370
+
371
+ /** Список готовых тем для переключателей в интерфейсе. */
372
+ export const themes = Object.entries(presets).map(([value, p]) => ({
373
+ value,
374
+ label: p.label,
375
+ accent: p.accent,
376
+ }))
377
+
378
+ // --- вспомогательное: смешивание цветов ---
379
+
380
+ function parseHex(hex) {
381
+ const s = hex.replace('#', '')
382
+ const full = s.length === 3 ? s.split('').map((c) => c + c).join('') : s
383
+ return [0, 2, 4].map((i) => parseInt(full.slice(i, i + 2), 16))
384
+ }
385
+
386
+ function toHex(rgb) {
387
+ return `#${rgb.map((c) => Math.round(Math.min(255, Math.max(0, c))).toString(16).padStart(2, '0')).join('')}`
388
+ }
389
+
390
+ /** Затемняет (amount < 0) или осветляет (amount > 0) цвет. */
391
+ function shade(hex, amount) {
392
+ const rgb = parseHex(hex)
393
+ const target = amount < 0 ? 0 : 255
394
+ return toHex(rgb.map((c) => c + (target - c) * Math.abs(amount)))
395
+ }
396
+
397
+ /** Смешивает цвет с белым: 0 — исходный, 1 — белый. */
398
+ function tint(hex, amount) {
399
+ return shade(hex, amount)
400
+ }
401
+
402
+ /** Смешивает два цвета: weight — доля первого. */
403
+ function mix(a, b, weight) {
404
+ const ra = parseHex(a)
405
+ const rb = parseHex(b)
406
+ return toHex(ra.map((c, i) => c * weight + rb[i] * (1 - weight)))
407
+ }
408
+
409
+ /** Цвет текста поверх заливки: белый на тёмном, почти чёрный на светлом. */
410
+ function onColor(hex) {
411
+ const [r, g, b] = parseHex(hex).map((c) => {
412
+ const v = c / 255
413
+ return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4
414
+ })
415
+ const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b
416
+ return luminance > 0.45 ? '#0f172a' : '#ffffff'
417
+ }
package/src/tokens.css ADDED
@@ -0,0 +1,109 @@
1
+ /*
2
+ * Дизайн-токены @krometra/ui-kit. Файл сгенерирован scripts/build-tokens.mjs из
3
+ * theme.js — править палитру там, затем `yarn tokens`.
4
+ *
5
+ * Светлая схема — по умолчанию; тёмная включается атрибутом
6
+ * <html data-k-scheme="dark"> или через applyTheme({ scheme: 'dark' | 'system' }).
7
+ * Все примитивы берут размеры и цвета отсюда, поэтому строка «инпут + селект +
8
+ * кнопка» выравнивается без правок в вёрстке приложения.
9
+ */
10
+
11
+ :root {
12
+ --k-primary: #2563eb;
13
+ --k-primary-hover: #1d4ed8;
14
+ --k-primary-soft: #eff6ff;
15
+ --k-on-primary: #ffffff;
16
+ --k-bg: #f6f8fb;
17
+ --k-surface: #ffffff;
18
+ --k-surface-muted: #f8fafc;
19
+ --k-border: #e4e9f0;
20
+ --k-border-strong: #cbd5e1;
21
+ --k-text: #172033;
22
+ --k-text-muted: #64748b;
23
+ --k-text-faint: #94a3b8;
24
+ --k-overlay: rgba(15, 23, 42, 0.35);
25
+ --k-tooltip-bg: #172033;
26
+ --k-tooltip-text: #ffffff;
27
+ --k-selection: #d3e0fb;
28
+ --k-radius-sm: 6px;
29
+ --k-radius: 8px;
30
+ --k-radius-lg: 14px;
31
+ --k-control-h: 2.5rem;
32
+ --k-control-h-sm: 2rem;
33
+ --k-gap: 0.5rem;
34
+ --k-shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.05);
35
+ --k-shadow-md: 0 4px 16px rgba(15, 23, 42, 0.08);
36
+ --k-shadow-lg: 0 18px 44px rgba(15, 23, 42, 0.14);
37
+ --k-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
38
+ --k-success: #059669;
39
+ --k-success-hover: #047857;
40
+ --k-success-soft: #ecfdf5;
41
+ --k-success-strong: #065f46;
42
+ --k-on-success: #ffffff;
43
+ --k-warning: #f59e0b;
44
+ --k-warning-hover: #d97706;
45
+ --k-warning-soft: #fffbeb;
46
+ --k-warning-strong: #b45309;
47
+ --k-on-warning: #ffffff;
48
+ --k-danger: #dc2626;
49
+ --k-danger-hover: #b91c1c;
50
+ --k-danger-soft: #fef2f2;
51
+ --k-danger-strong: #991b1b;
52
+ --k-on-danger: #ffffff;
53
+ --k-info: #4f46e5;
54
+ --k-info-hover: #4338ca;
55
+ --k-info-soft: #eef2ff;
56
+ --k-info-strong: #3730a3;
57
+ --k-on-info: #ffffff;
58
+ color-scheme: light;
59
+ }
60
+
61
+ :root[data-k-scheme="dark"] {
62
+ --k-primary: #3f76ed;
63
+ --k-primary-hover: #5a89f0;
64
+ --k-primary-soft: #1b2e56;
65
+ --k-on-primary: #ffffff;
66
+ --k-bg: #0b1220;
67
+ --k-surface: #111a2b;
68
+ --k-surface-muted: #18233a;
69
+ --k-border: #24314a;
70
+ --k-border-strong: #354764;
71
+ --k-text: #e5eaf3;
72
+ --k-text-muted: #9aa7bb;
73
+ --k-text-faint: #66748c;
74
+ --k-overlay: rgba(0, 0, 0, 0.6);
75
+ --k-tooltip-bg: #e5eaf3;
76
+ --k-tooltip-text: #0b1220;
77
+ --k-selection: #233f79;
78
+ --k-radius-sm: 6px;
79
+ --k-radius: 8px;
80
+ --k-radius-lg: 14px;
81
+ --k-control-h: 2.5rem;
82
+ --k-control-h-sm: 2rem;
83
+ --k-gap: 0.5rem;
84
+ --k-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35);
85
+ --k-shadow-md: 0 4px 16px rgba(0, 0, 0, 0.45);
86
+ --k-shadow-lg: 0 18px 44px rgba(0, 0, 0, 0.6);
87
+ --k-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
88
+ --k-success: #34d399;
89
+ --k-success-hover: #6ee7b7;
90
+ --k-success-soft: #173b3f;
91
+ --k-success-strong: #6ee7b7;
92
+ --k-on-success: #0f172a;
93
+ --k-warning: #fbbf24;
94
+ --k-warning-hover: #fcd34d;
95
+ --k-warning-soft: #3b382a;
96
+ --k-warning-strong: #fcd34d;
97
+ --k-on-warning: #0f172a;
98
+ --k-danger: #f87171;
99
+ --k-danger-hover: #fca5a5;
100
+ --k-danger-soft: #3b2a38;
101
+ --k-danger-strong: #fca5a5;
102
+ --k-on-danger: #ffffff;
103
+ --k-info: #818cf8;
104
+ --k-info-hover: #a5b4fc;
105
+ --k-info-soft: #252f50;
106
+ --k-info-strong: #a5b4fc;
107
+ --k-on-info: #ffffff;
108
+ color-scheme: dark;
109
+ }