@i18n-micro/devtools-ui 1.0.1 → 1.0.3

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.
package/vite/plugin.ts ADDED
@@ -0,0 +1,576 @@
1
+ import type { PluginOption, ViteDevServer, IndexHtmlTransformResult } from 'vite'
2
+ import type { IncomingMessage, ServerResponse } from 'node:http'
3
+ import * as fs from 'node:fs'
4
+ import * as path from 'node:path'
5
+ import { readdir } from 'node:fs/promises'
6
+
7
+ export interface DevToolsPluginOptions {
8
+ base?: string
9
+ translationDir?: string
10
+ injectButton?: boolean
11
+ }
12
+
13
+ // Вспомогательная функция для безопасного резолва пути
14
+ function safeResolvePath(projectRoot: string, filePath: string): string {
15
+ const normalizedFile = filePath.replace(/^\/+/, '').replace(/\/+/g, '/')
16
+ const resolvedPath = path.resolve(projectRoot, normalizedFile)
17
+ const normalizedRoot = path.resolve(projectRoot)
18
+ const normalizedFilePath = path.resolve(resolvedPath)
19
+
20
+ if (!normalizedFilePath.startsWith(normalizedRoot)) {
21
+ throw new Error(`Access denied: Path ${resolvedPath} is outside project root`)
22
+ }
23
+
24
+ return resolvedPath
25
+ }
26
+
27
+ // Рекурсивное сканирование директории для поиска JSON файлов
28
+ async function scanTranslationFiles(dir: string, baseDir: string): Promise<string[]> {
29
+ const files: string[] = []
30
+ try {
31
+ const entries = await readdir(dir, { withFileTypes: true })
32
+ for (const entry of entries) {
33
+ const fullPath = path.join(dir, entry.name)
34
+ if (entry.isDirectory()) {
35
+ const subFiles = await scanTranslationFiles(fullPath, baseDir)
36
+ files.push(...subFiles)
37
+ }
38
+ else if (entry.isFile() && entry.name.endsWith('.json')) {
39
+ const relativePath = path.relative(baseDir, fullPath)
40
+ files.push(relativePath.replace(/\\/g, '/')) // Нормализуем для кроссплатформенности
41
+ }
42
+ }
43
+ }
44
+ catch (error) {
45
+ // Игнорируем ошибки доступа к директориям
46
+ console.warn(`[i18n-devtools] Cannot scan directory ${dir}:`, error)
47
+ }
48
+ return files
49
+ }
50
+
51
+ // Скрипт для инжекции кнопки
52
+ const BUTTON_INJECTION_SCRIPT = `
53
+ (function() {
54
+ if (typeof window === 'undefined' || document.getElementById('i18n-devtools-button-container')) {
55
+ return;
56
+ }
57
+
58
+ const container = document.createElement('div');
59
+ container.id = 'i18n-devtools-button-container';
60
+ container.style.cssText = 'position: fixed; bottom: 20px; right: 20px; z-index: 99999; pointer-events: none;';
61
+ document.body.appendChild(container);
62
+
63
+ const button = document.createElement('button');
64
+ button.id = 'i18n-devtools-button';
65
+ button.type = 'button';
66
+ button.innerHTML = '<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" style="display: inline-block; vertical-align: middle; margin-right: 8px;"><circle cx="12" cy="12" r="10" /><line x1="2" y1="12" x2="22" y2="12" /><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" /></svg>i18n';
67
+ button.style.cssText = 'cursor: pointer; background: #1e1e1e; color: white; padding: 8px 16px; border-radius: 9999px; display: flex; align-items: center; gap: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); border: 1px solid #333; font-size: 14px; font-weight: 600; transition: all 0.3s ease; opacity: 0.3; pointer-events: auto; font-family: system-ui, sans-serif;';
68
+
69
+ let hideTimeout = null;
70
+ let isVisible = true;
71
+ let lastMouseY = window.innerHeight;
72
+
73
+ function showButton() {
74
+ if (!isVisible) {
75
+ button.style.transform = 'translateX(0)';
76
+ button.style.opacity = '0.3';
77
+ isVisible = true;
78
+ }
79
+ if (hideTimeout) {
80
+ clearTimeout(hideTimeout);
81
+ hideTimeout = null;
82
+ }
83
+ }
84
+
85
+ function hideButton() {
86
+ if (isVisible) {
87
+ // Оставляем видимой только небольшую часть кнопки (примерно 40px)
88
+ button.style.transform = 'translateX(calc(100% - 40px))';
89
+ button.style.opacity = '0.3';
90
+ isVisible = false;
91
+ }
92
+ }
93
+
94
+ function checkMouseDistance(e) {
95
+ const buttonRect = button.getBoundingClientRect();
96
+ const buttonCenterX = buttonRect.left + buttonRect.width / 2;
97
+ const buttonCenterY = buttonRect.top + buttonRect.height / 2;
98
+ const distanceX = Math.abs(e.clientX - buttonCenterX);
99
+ const distanceY = Math.abs(e.clientY - buttonCenterY);
100
+ const distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
101
+
102
+ if (distance > 200 && !button.matches(':hover')) {
103
+ if (!hideTimeout) {
104
+ hideTimeout = setTimeout(hideButton, 1000);
105
+ }
106
+ } else {
107
+ if (hideTimeout) {
108
+ clearTimeout(hideTimeout);
109
+ hideTimeout = null;
110
+ }
111
+ if (!isVisible) {
112
+ showButton();
113
+ }
114
+ }
115
+ lastMouseY = e.clientY;
116
+ }
117
+
118
+ button.addEventListener('mouseenter', function() {
119
+ this.style.opacity = '1';
120
+ this.style.transform = 'translateX(0) translateY(-2px) scale(1.05)';
121
+ if (hideTimeout) {
122
+ clearTimeout(hideTimeout);
123
+ hideTimeout = null;
124
+ }
125
+ // Показываем кнопку полностью при наведении
126
+ if (!isVisible) {
127
+ showButton();
128
+ }
129
+ });
130
+
131
+ button.addEventListener('mouseleave', function() {
132
+ if (isVisible) {
133
+ this.style.opacity = '0.3';
134
+ this.style.transform = 'translateX(0) scale(1)';
135
+ hideTimeout = setTimeout(hideButton, 3000);
136
+ }
137
+ });
138
+
139
+ button.addEventListener('click', function() {
140
+ // Проверяем, не открыт ли уже редактор
141
+ if (document.getElementById('i18n-devtools-modal')) {
142
+ return;
143
+ }
144
+
145
+ // Открываем модальное окно с iframe
146
+ const modal = document.createElement('div');
147
+ modal.id = 'i18n-devtools-modal';
148
+ modal.style.cssText = 'position: fixed; inset: 0; z-index: 999999; background: rgba(0, 0, 0, 0.2); backdrop-filter: blur(2px); display: flex; align-items: center; justify-content: center; padding: 2rem;';
149
+ modal.addEventListener('click', function(e) {
150
+ if (e.target === modal) {
151
+ document.body.removeChild(modal);
152
+ }
153
+ });
154
+
155
+ const content = document.createElement('div');
156
+ content.style.cssText = 'background: white; width: 90vw; max-width: 1200px; height: 85vh; border-radius: 12px; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1); display: flex; flex-direction: column; overflow: hidden; border: 1px solid #e5e7eb;';
157
+ content.addEventListener('click', function(e) {
158
+ e.stopPropagation();
159
+ });
160
+
161
+ const header = document.createElement('div');
162
+ header.style.cssText = 'display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; border-bottom: 1px solid #e2e8f0; background: #f8fafc; flex-shrink: 0;';
163
+ header.innerHTML = '<div style="font-weight: 600; color: #334155; display: flex; align-items: center; gap: 8px;"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10" /><line x1="2" y1="12" x2="22" y2="12" /><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" /></svg> i18n DevTools</div><button id="i18n-devtools-close" style="background: transparent; border: none; cursor: pointer; padding: 4px; border-radius: 4px; display: flex; color: #64748b; transition: background 0.2s;">✕</button>';
164
+
165
+ const closeBtn = header.querySelector('#i18n-devtools-close');
166
+ closeBtn.addEventListener('mouseenter', function() {
167
+ this.style.background = '#e2e8f0';
168
+ });
169
+ closeBtn.addEventListener('mouseleave', function() {
170
+ this.style.background = 'transparent';
171
+ });
172
+ closeBtn.addEventListener('click', function() {
173
+ document.body.removeChild(modal);
174
+ });
175
+
176
+ const iframeContainer = document.createElement('div');
177
+ iframeContainer.style.cssText = 'flex: 1; position: relative; overflow: hidden; background: white;';
178
+
179
+ const iframe = document.createElement('iframe');
180
+ iframe.id = 'i18n-devtools-iframe';
181
+ iframe.style.cssText = 'width: 100%; height: 100%; border: none; background: white;';
182
+ iframe.src = '/__i18n_devtools.html';
183
+ iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups');
184
+
185
+ iframeContainer.appendChild(iframe);
186
+ content.appendChild(header);
187
+ content.appendChild(iframeContainer);
188
+ modal.appendChild(content);
189
+ document.body.appendChild(modal);
190
+ });
191
+
192
+ document.addEventListener('mousemove', checkMouseDistance);
193
+
194
+ // Автоматически скрываем через 3 секунды после загрузки
195
+ setTimeout(() => {
196
+ if (!button.matches(':hover')) {
197
+ hideTimeout = setTimeout(hideButton, 3000);
198
+ }
199
+ }, 3000);
200
+
201
+ container.appendChild(button);
202
+ })();
203
+ `
204
+
205
+ export function i18nDevToolsPlugin(options: DevToolsPluginOptions = {}): PluginOption {
206
+ const apiBase = options.base || '/__i18n_api'
207
+ const translationDir = options.translationDir || 'src/locales'
208
+ const injectButton = options.injectButton !== false // По умолчанию true
209
+
210
+ return {
211
+ name: 'i18n-micro-devtools-plugin',
212
+ apply: 'serve', // Работает только в dev server
213
+
214
+ resolveId(id: string) {
215
+ if (id === '/@vite-plugin-i18n-devtools/devtools-ui.js') {
216
+ return id
217
+ }
218
+ return null
219
+ },
220
+
221
+ load(id: string) {
222
+ if (id === '/@vite-plugin-i18n-devtools/devtools-ui.js') {
223
+ // Возвращаем реэкспорт из devtools-ui пакета
224
+ return `export * from '@i18n-micro/devtools-ui'`
225
+ }
226
+ return null
227
+ },
228
+
229
+ transformIndexHtml(html: string): IndexHtmlTransformResult {
230
+ if (!injectButton) {
231
+ return html
232
+ }
233
+
234
+ // Инжектируем скрипт перед закрывающим тегом </body>
235
+ const scriptTag = `<script>${BUTTON_INJECTION_SCRIPT}</script>`
236
+ if (html.includes('</body>')) {
237
+ return html.replace('</body>', `${scriptTag}</body>`)
238
+ }
239
+ // Если нет </body>, добавляем в конец
240
+ return html + scriptTag
241
+ },
242
+
243
+ configureServer(server: ViteDevServer) {
244
+ // Используем server.config.root как источник правды для корня проекта
245
+ const projectRoot = server.config.root
246
+
247
+ // HTML страница для iframe с devtools UI
248
+ // Используем виртуальный модуль для правильного импорта
249
+ const devtoolsHtml = `<!DOCTYPE html>
250
+ <html lang="en">
251
+ <head>
252
+ <meta charset="UTF-8">
253
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
254
+ <title>i18n DevTools</title>
255
+ <style>
256
+ * {
257
+ margin: 0;
258
+ padding: 0;
259
+ box-sizing: border-box;
260
+ }
261
+ body {
262
+ font-family: system-ui, -apple-system, sans-serif;
263
+ overflow: hidden;
264
+ height: 100vh;
265
+ }
266
+ #app {
267
+ width: 100%;
268
+ height: 100%;
269
+ }
270
+ .loading {
271
+ display: flex;
272
+ align-items: center;
273
+ justify-content: center;
274
+ height: 100%;
275
+ color: #64748b;
276
+ }
277
+ </style>
278
+ </head>
279
+ <body>
280
+ <div id="app">
281
+ <div class="loading">Loading DevTools...</div>
282
+ </div>
283
+ <script type="module">
284
+ // Импортируем devtools UI
285
+ // Пытаемся импортировать через разные пути для совместимости
286
+ let register;
287
+ try {
288
+ const devtoolsModule = await import('/@vite-plugin-i18n-devtools/devtools-ui.js');
289
+ register = devtoolsModule.register;
290
+ } catch (e) {
291
+ // Fallback: пытаемся импортировать напрямую
292
+ try {
293
+ const devtoolsModule = await import('@i18n-micro/devtools-ui');
294
+ register = devtoolsModule.register;
295
+ } catch (e2) {
296
+ console.error('Failed to load devtools UI:', e2);
297
+ document.getElementById('app').innerHTML = '<div class="loading" style="color: #ef4444;">Failed to load DevTools. Please ensure @i18n-micro/devtools-ui is installed.</div>';
298
+ throw e2;
299
+ }
300
+ }
301
+
302
+ // Регистрируем custom element
303
+ register();
304
+
305
+ // Создаем bridge через API
306
+ const bridge = {
307
+ async getLocalesAndTranslations() {
308
+ try {
309
+ const response = await fetch('/__i18n_api/files');
310
+ const data = await response.json();
311
+ const result = {};
312
+ for (const file of data.files || []) {
313
+ try {
314
+ const fileResponse = await fetch('/__i18n_api/file?path=' + encodeURIComponent(file));
315
+ const fileData = await fileResponse.json();
316
+ if (fileData.success && fileData.content) {
317
+ result[file] = fileData.content;
318
+ }
319
+ } catch (e) {
320
+ console.warn('Failed to load file:', file, e);
321
+ }
322
+ }
323
+ return result;
324
+ } catch (e) {
325
+ console.error('Failed to get locales:', e);
326
+ return {};
327
+ }
328
+ },
329
+
330
+ async getConfigs() {
331
+ try {
332
+ const response = await fetch('/__i18n_api/config');
333
+ return await response.json();
334
+ } catch (e) {
335
+ return {
336
+ defaultLocale: 'en',
337
+ fallbackLocale: 'en',
338
+ locales: [],
339
+ translationDir: '${translationDir}',
340
+ };
341
+ }
342
+ },
343
+
344
+ async saveTranslation(filePath, content) {
345
+ const response = await fetch('/__i18n_api/save', {
346
+ method: 'POST',
347
+ headers: { 'Content-Type': 'application/json' },
348
+ body: JSON.stringify({ file: filePath, content }),
349
+ });
350
+ if (!response.ok) {
351
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }));
352
+ throw new Error(error.error || 'Failed to save');
353
+ }
354
+ },
355
+
356
+ onLocalesUpdate(callback) {
357
+ let interval = null;
358
+ interval = setInterval(async () => {
359
+ try {
360
+ const data = await bridge.getLocalesAndTranslations();
361
+ callback(data);
362
+ } catch (e) {
363
+ console.error('Failed to update locales:', e);
364
+ }
365
+ }, 2000);
366
+ return () => {
367
+ if (interval) {
368
+ clearInterval(interval);
369
+ }
370
+ };
371
+ },
372
+ };
373
+
374
+ const app = document.getElementById('app');
375
+ app.innerHTML = '';
376
+ const element = document.createElement('i18n-devtools-ui');
377
+ element.bridge = bridge;
378
+ element.style.cssText = 'width: 100%; height: 100%; display: block;';
379
+ app.appendChild(element);
380
+ </script>
381
+ </body>
382
+ </html>`
383
+
384
+ // Middleware для обслуживания HTML страницы devtools
385
+ server.middlewares.use('/__i18n_devtools.html', (_req: IncomingMessage, res: ServerResponse) => {
386
+ res.statusCode = 200
387
+ res.setHeader('Content-Type', 'text/html; charset=utf-8')
388
+ res.end(devtoolsHtml)
389
+ })
390
+
391
+ // Эндпоинт для получения конфигурации
392
+ server.middlewares.use(`${apiBase}/config`, async (_req: IncomingMessage, res: ServerResponse, next) => {
393
+ if (_req.method !== 'GET') {
394
+ return next()
395
+ }
396
+
397
+ try {
398
+ res.statusCode = 200
399
+ res.setHeader('Content-Type', 'application/json')
400
+ res.end(JSON.stringify({
401
+ defaultLocale: 'en',
402
+ fallbackLocale: 'en',
403
+ locales: [],
404
+ translationDir,
405
+ }))
406
+ }
407
+ catch (e) {
408
+ console.error('[i18n-devtools] Config error:', e)
409
+ res.statusCode = 500
410
+ res.setHeader('Content-Type', 'application/json')
411
+ res.end(JSON.stringify({
412
+ success: false,
413
+ error: e instanceof Error ? e.message : String(e),
414
+ }))
415
+ }
416
+ })
417
+
418
+ // Эндпоинт для получения списка файлов
419
+ server.middlewares.use(`${apiBase}/files`, async (req: IncomingMessage, res: ServerResponse, next) => {
420
+ if (req.method !== 'GET') {
421
+ return next()
422
+ }
423
+
424
+ try {
425
+ const localesPath = path.resolve(projectRoot, translationDir)
426
+
427
+ // Проверяем, что путь безопасен
428
+ safeResolvePath(projectRoot, translationDir)
429
+
430
+ if (!fs.existsSync(localesPath)) {
431
+ res.statusCode = 200
432
+ res.setHeader('Content-Type', 'application/json')
433
+ res.end(JSON.stringify({ files: [], structure: {} }))
434
+ return
435
+ }
436
+
437
+ const files = await scanTranslationFiles(localesPath, localesPath)
438
+
439
+ // Строим структуру директорий
440
+ const structure: Record<string, unknown> = {}
441
+ for (const file of files) {
442
+ const parts = file.split('/')
443
+ let current = structure
444
+ for (let i = 0; i < parts.length - 1; i++) {
445
+ const part = parts[i]
446
+ if (!current[part]) {
447
+ current[part] = {}
448
+ }
449
+ current = current[part] as Record<string, unknown>
450
+ }
451
+ current[parts[parts.length - 1]] = file
452
+ }
453
+
454
+ res.statusCode = 200
455
+ res.setHeader('Content-Type', 'application/json')
456
+ res.end(JSON.stringify({ files, structure }))
457
+ }
458
+ catch (e) {
459
+ console.error('[i18n-devtools] Files list error:', e)
460
+ res.statusCode = 500
461
+ res.setHeader('Content-Type', 'application/json')
462
+ res.end(JSON.stringify({
463
+ success: false,
464
+ error: e instanceof Error ? e.message : String(e),
465
+ }))
466
+ }
467
+ })
468
+
469
+ // Эндпоинт для загрузки конкретного файла
470
+ server.middlewares.use(`${apiBase}/file`, async (req: IncomingMessage, res: ServerResponse, next) => {
471
+ if (req.method !== 'GET') {
472
+ return next()
473
+ }
474
+
475
+ try {
476
+ const url = new URL(req.url || '', `http://${req.headers.host}`)
477
+ const filePath = url.searchParams.get('path')
478
+
479
+ if (!filePath) {
480
+ throw new Error('Path parameter is required')
481
+ }
482
+
483
+ // Резолвим путь относительно translationDir
484
+ const fullPath = path.join(translationDir, filePath)
485
+ const resolvedPath = safeResolvePath(projectRoot, fullPath)
486
+
487
+ if (!fs.existsSync(resolvedPath)) {
488
+ res.statusCode = 404
489
+ res.setHeader('Content-Type', 'application/json')
490
+ res.end(JSON.stringify({ success: false, error: 'File not found' }))
491
+ return
492
+ }
493
+
494
+ if (!resolvedPath.endsWith('.json')) {
495
+ throw new Error('Invalid file: only .json files are allowed')
496
+ }
497
+
498
+ const content = fs.readFileSync(resolvedPath, 'utf-8')
499
+ const parsed = JSON.parse(content)
500
+
501
+ res.statusCode = 200
502
+ res.setHeader('Content-Type', 'application/json')
503
+ res.end(JSON.stringify({ success: true, content: parsed, path: filePath }))
504
+ }
505
+ catch (e) {
506
+ console.error('[i18n-devtools] File read error:', e)
507
+ res.statusCode = 500
508
+ res.setHeader('Content-Type', 'application/json')
509
+ res.end(JSON.stringify({
510
+ success: false,
511
+ error: e instanceof Error ? e.message : String(e),
512
+ }))
513
+ }
514
+ })
515
+
516
+ // Эндпоинт для сохранения файла
517
+ server.middlewares.use(`${apiBase}/save`, async (req: IncomingMessage, res: ServerResponse, next) => {
518
+ if (req.method !== 'POST') {
519
+ return next()
520
+ }
521
+
522
+ try {
523
+ // Чтение тела запроса
524
+ const buffers: Buffer[] = []
525
+ for await (const chunk of req) {
526
+ buffers.push(chunk)
527
+ }
528
+ const bodyData = Buffer.concat(buffers).toString()
529
+
530
+ if (!bodyData) {
531
+ throw new Error('Empty request body')
532
+ }
533
+
534
+ const body = JSON.parse(bodyData)
535
+ const { file, content } = body
536
+
537
+ if (!file || !content) {
538
+ throw new Error('Invalid data: file and content are required')
539
+ }
540
+
541
+ // Резолвим путь относительно translationDir, если путь не абсолютный
542
+ const fullPath = file.startsWith(translationDir) ? file : path.join(translationDir, file)
543
+ const filePath = safeResolvePath(projectRoot, fullPath)
544
+
545
+ // Проверка расширения
546
+ if (!filePath.endsWith('.json')) {
547
+ throw new Error('Invalid file: only .json files are allowed')
548
+ }
549
+
550
+ // Создаем директорию, если её нет
551
+ const dir = path.dirname(filePath)
552
+ if (!fs.existsSync(dir)) {
553
+ fs.mkdirSync(dir, { recursive: true })
554
+ }
555
+
556
+ // Записываем файл
557
+ fs.writeFileSync(filePath, JSON.stringify(content, null, 2), 'utf-8')
558
+
559
+ // Успешный ответ
560
+ res.statusCode = 200
561
+ res.setHeader('Content-Type', 'application/json')
562
+ res.end(JSON.stringify({ success: true }))
563
+ }
564
+ catch (e) {
565
+ console.error('[i18n-devtools] Save error:', e)
566
+ res.statusCode = 500
567
+ res.setHeader('Content-Type', 'application/json')
568
+ res.end(JSON.stringify({
569
+ success: false,
570
+ error: e instanceof Error ? e.message : String(e),
571
+ }))
572
+ }
573
+ })
574
+ },
575
+ } as PluginOption
576
+ }
package/dist/style.css DELETED
@@ -1 +0,0 @@
1
- /*! tailwindcss v4.1.17 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-orange-100:oklch(95.4% .038 75.164);--color-orange-600:oklch(64.6% .222 41.116);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-teal-600:oklch(60% .118 184.704);--color-cyan-600:oklch(60.9% .126 221.723);--color-blue-50:oklch(97% .014 254.604);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-indigo-600:oklch(51.1% .262 276.966);--color-purple-100:oklch(94.6% .033 307.174);--color-purple-600:oklch(55.8% .288 302.321);--color-pink-600:oklch(59.2% .249 .584);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:oklch(27.9% .041 260.031);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-6xl:3.75rem;--text-6xl--line-height:1;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-font-feature-settings:var(--font-sans--font-feature-settings);--default-font-variation-settings:var(--font-sans--font-variation-settings);--default-mono-font-family:var(--font-mono);--default-mono-font-feature-settings:var(--font-mono--font-feature-settings);--default-mono-font-variation-settings:var(--font-mono--font-variation-settings)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}body{line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1;color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentColor 50%,transparent)}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing)*0)}.top-0{top:calc(var(--spacing)*0)}.top-1\/2{top:50%}.right-0{right:calc(var(--spacing)*0)}.right-2{right:calc(var(--spacing)*2)}.bottom-0{bottom:calc(var(--spacing)*0)}.left-0{left:calc(var(--spacing)*0)}.z-20{z-index:20}.z-50{z-index:50}.z-\[100\]{z-index:100}.my-1{margin-block:calc(var(--spacing)*1)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-4{margin-top:calc(var(--spacing)*4)}.-mr-2{margin-right:calc(var(--spacing)*-2)}.mr-2{margin-right:calc(var(--spacing)*2)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.ml-2{margin-left:calc(var(--spacing)*2)}.ml-3{margin-left:calc(var(--spacing)*3)}.ml-4{margin-left:calc(var(--spacing)*4)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-flex{display:inline-flex}.h-3{height:calc(var(--spacing)*3)}.h-4{height:calc(var(--spacing)*4)}.h-6{height:calc(var(--spacing)*6)}.h-full{height:100%}.min-h-0{min-height:calc(var(--spacing)*0)}.min-h-\[38px\]{min-height:38px}.w-2{width:calc(var(--spacing)*2)}.w-3{width:calc(var(--spacing)*3)}.w-4{width:calc(var(--spacing)*4)}.w-6{width:calc(var(--spacing)*6)}.w-\[1px\]{width:1px}.w-full{width:100%}.min-w-\[60px\]{min-width:60px}.flex-1{flex:1}.shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.cursor-col-resize{cursor:col-resize}.cursor-pointer{cursor:pointer}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-2{gap:calc(var(--spacing)*2)}.gap-4{gap:calc(var(--spacing)*4)}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*3)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-x-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.overscroll-contain{overscroll-behavior:contain}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-blue-100{border-color:var(--color-blue-100)}.border-blue-200{border-color:var(--color-blue-200)}.border-blue-500{border-color:var(--color-blue-500)}.border-gray-100{border-color:var(--color-gray-100)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-green-200{border-color:var(--color-green-200)}.border-red-200{border-color:var(--color-red-200)}.border-slate-100{border-color:var(--color-slate-100)}.border-slate-200{border-color:var(--color-slate-200)}.border-transparent{border-color:#0000}.border-l-yellow-500{border-left-color:var(--color-yellow-500)}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab,red,red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black)50%,transparent)}}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-300{background-color:var(--color-gray-300)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-orange-100{background-color:var(--color-orange-100)}.bg-purple-100{background-color:var(--color-purple-100)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-100{background-color:var(--color-red-100)}.bg-red-600{background-color:var(--color-red-600)}.bg-slate-50{background-color:var(--color-slate-50)}.bg-slate-100{background-color:var(--color-slate-100)}.bg-slate-200{background-color:var(--color-slate-200)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-yellow-100{background-color:var(--color-yellow-100)}.bg-gradient-to-r{--tw-gradient-position:to right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-slate-50{--tw-gradient-from:var(--color-slate-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.p-1{padding:calc(var(--spacing)*1)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.p-8{padding:calc(var(--spacing)*8)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-4{padding-block:calc(var(--spacing)*4)}.pt-4{padding-top:calc(var(--spacing)*4)}.pr-8{padding-right:calc(var(--spacing)*8)}.pb-3{padding-bottom:calc(var(--spacing)*3)}.pl-4{padding-left:calc(var(--spacing)*4)}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.break-all{word-break:break-all}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-cyan-600{color:var(--color-cyan-600)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-green-500{color:var(--color-green-500)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-indigo-600{color:var(--color-indigo-600)}.text-orange-600{color:var(--color-orange-600)}.text-pink-600{color:var(--color-pink-600)}.text-purple-600{color:var(--color-purple-600)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-800{color:var(--color-red-800)}.text-slate-400{color:var(--color-slate-400)}.text-slate-600{color:var(--color-slate-600)}.text-slate-700{color:var(--color-slate-700)}.text-teal-600{color:var(--color-teal-600)}.text-white{color:var(--color-white)}.text-yellow-600{color:var(--color-yellow-600)}.text-yellow-700{color:var(--color-yellow-700)}.text-yellow-800{color:var(--color-yellow-800)}.capitalize{text-transform:capitalize}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.placeholder-slate-400::placeholder{color:var(--color-slate-400)}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline-hidden{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.outline-hidden{outline-offset:2px;outline:2px solid #0000}}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.ring-inset{--tw-ring-inset:inset}@media (hover:hover){.hover\:bg-blue-50\/50:hover{background-color:#eff6ff80}@supports (color:color-mix(in lab,red,red)){.hover\:bg-blue-50\/50:hover{background-color:color-mix(in oklab,var(--color-blue-50)50%,transparent)}}.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:bg-slate-100:hover{background-color:var(--color-slate-100)}.hover\:bg-slate-300:hover{background-color:var(--color-slate-300)}.hover\:text-blue-600:hover{color:var(--color-blue-600)}.hover\:text-gray-600:hover{color:var(--color-gray-600)}.hover\:text-slate-800:hover{color:var(--color-slate-800)}}.focus\:border-blue-400:focus{border-color:var(--color-blue-400)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-blue-200:focus{--tw-ring-color:var(--color-blue-200)}.focus\:ring-blue-500:focus{--tw-ring-color:var(--color-blue-500)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:outline-hidden:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.focus\:outline-hidden:focus{outline-offset:2px;outline:2px solid #0000}}.disabled\:opacity-50:disabled{opacity:.5}}.i18n-devtools{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}