file-share-mcp 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/dist/gui.js CHANGED
@@ -4,49 +4,37 @@ import cors from 'cors';
4
4
  import path from 'path';
5
5
  import { fileURLToPath } from 'url';
6
6
  import fs from 'fs';
7
+ import os from 'os';
7
8
  import axios from 'axios';
8
9
  import { setupLogging } from './utils/logger.js';
9
10
  import { getConfig, saveConfig } from './utils/config-manager.js';
10
- import { getSharedFiles, validateSafePath, deleteFile } from './utils/file-manager.js';
11
+ import { getSharedFiles, validateSafePath, deleteFile, createDirectory, moveFile, deleteDirectory } from './utils/file-manager.js';
11
12
  const __filename = fileURLToPath(import.meta.url);
12
13
  const __dirname = path.dirname(__filename);
13
14
  setupLogging('GUI');
14
15
  const app = express();
15
16
  app.use(cors());
16
17
  app.use(express.json());
17
- // Protect stdout - setupLogging already redirects console.log/error
18
- // but we want to be sure no other library writes to stdout.
19
- // Multer configuration for file uploads
20
- const storage = multer.diskStorage({
21
- destination: (req, file, cb) => {
22
- const config = getConfig();
23
- if (!fs.existsSync(config.shared_folder_path)) {
24
- fs.mkdirSync(config.shared_folder_path, { recursive: true });
25
- }
26
- cb(null, config.shared_folder_path);
27
- },
28
- filename: (req, file, cb) => {
29
- cb(null, file.originalname);
30
- }
31
- });
32
- const upload = multer({ storage });
18
+ const upload = multer({ dest: os.tmpdir() });
33
19
  // Endpoints
34
20
  app.get('/proxy/health', (req, res) => {
35
21
  res.json({ status: 'healthy' });
36
22
  });
37
23
  app.get('/api/files', (req, res) => {
38
24
  try {
39
- const files = getSharedFiles();
40
- res.json(files);
25
+ const relativePath = req.query.path || '';
26
+ const entries = getSharedFiles(relativePath);
27
+ res.json(entries);
41
28
  }
42
29
  catch (error) {
43
30
  res.status(500).json({ error: error.message });
44
31
  }
45
32
  });
46
- app.get('/api/download/:filename', (req, res) => {
33
+ app.get('/api/download', (req, res) => {
47
34
  try {
48
- const safePath = validateSafePath(req.params.filename);
49
- if (fs.existsSync(safePath)) {
35
+ const relativePath = req.query.path || '';
36
+ const safePath = validateSafePath(relativePath);
37
+ if (fs.existsSync(safePath) && fs.statSync(safePath).isFile()) {
50
38
  res.download(safePath);
51
39
  }
52
40
  else {
@@ -57,9 +45,10 @@ app.get('/api/download/:filename', (req, res) => {
57
45
  res.status(400).send(error.message);
58
46
  }
59
47
  });
60
- app.delete('/api/files/:filename', (req, res) => {
48
+ app.delete('/api/files', (req, res) => {
61
49
  try {
62
- deleteFile(req.params.filename);
50
+ const relativePath = req.query.path || '';
51
+ deleteFile(relativePath);
63
52
  res.json({ message: 'File deleted successfully' });
64
53
  }
65
54
  catch (error) {
@@ -68,13 +57,14 @@ app.delete('/api/files/:filename', (req, res) => {
68
57
  });
69
58
  const PREVIEWABLE_EXTENSIONS = ['.txt', '.md'];
70
59
  const PREVIEW_MAX_SIZE = 1024 * 1024; // 1 MB
71
- app.get('/api/preview/:filename', (req, res) => {
60
+ app.get('/api/preview', (req, res) => {
72
61
  try {
73
- const ext = path.extname(req.params.filename).toLowerCase();
62
+ const relativePath = req.query.path || '';
63
+ const ext = path.extname(relativePath).toLowerCase();
74
64
  if (!PREVIEWABLE_EXTENSIONS.includes(ext)) {
75
65
  return res.status(400).json({ error: 'Preview not supported for this file type' });
76
66
  }
77
- const safePath = validateSafePath(req.params.filename);
67
+ const safePath = validateSafePath(relativePath);
78
68
  if (!fs.existsSync(safePath)) {
79
69
  return res.status(404).json({ error: 'File not found' });
80
70
  }
@@ -90,11 +80,56 @@ app.get('/api/preview/:filename', (req, res) => {
90
80
  }
91
81
  });
92
82
  app.post('/api/upload', upload.single('file'), (req, res) => {
93
- if (req.file) {
94
- res.json({ message: 'File uploaded successfully', filename: req.file.filename });
83
+ if (!req.file)
84
+ return res.status(400).json({ error: 'No file uploaded' });
85
+ const targetPath = req.body.targetPath || '';
86
+ try {
87
+ const dir = validateSafePath(targetPath);
88
+ fs.mkdirSync(dir, { recursive: true });
89
+ const dest = path.join(dir, path.basename(req.file.originalname));
90
+ fs.renameSync(req.file.path, dest);
91
+ res.json({ message: 'File uploaded successfully', filename: path.basename(req.file.originalname) });
95
92
  }
96
- else {
97
- res.status(400).json({ error: 'No file uploaded' });
93
+ catch (err) {
94
+ try {
95
+ fs.unlinkSync(req.file.path);
96
+ }
97
+ catch { }
98
+ res.status(400).json({ error: err.message });
99
+ }
100
+ });
101
+ app.delete('/api/folder', (req, res) => {
102
+ try {
103
+ const relativePath = req.query.path || '';
104
+ deleteDirectory(relativePath);
105
+ res.json({ message: 'Directory deleted successfully' });
106
+ }
107
+ catch (error) {
108
+ res.status(400).json({ error: error.message });
109
+ }
110
+ });
111
+ app.post('/api/mkdir', (req, res) => {
112
+ try {
113
+ const { path: dirPath } = req.body;
114
+ if (!dirPath)
115
+ return res.status(400).json({ error: 'path is required' });
116
+ createDirectory(dirPath);
117
+ res.json({ message: 'Directory created successfully' });
118
+ }
119
+ catch (error) {
120
+ res.status(400).json({ error: error.message });
121
+ }
122
+ });
123
+ app.post('/api/move', (req, res) => {
124
+ try {
125
+ const { from, to } = req.body;
126
+ if (!from || !to)
127
+ return res.status(400).json({ error: 'from and to are required' });
128
+ moveFile(from, to);
129
+ res.json({ message: 'File moved successfully' });
130
+ }
131
+ catch (error) {
132
+ res.status(400).json({ error: error.message });
98
133
  }
99
134
  });
100
135
  app.get('/api/config', (req, res) => {
@@ -155,7 +190,6 @@ async function registerWithProxy() {
155
190
  if (!config.PROXY_URL || !config.PROXY_APP_PATH)
156
191
  return null;
157
192
  try {
158
- // Check if proxy is up
159
193
  await axios.get(`${config.PROXY_URL}/proxy/health`, { timeout: 2000 });
160
194
  const response = await axios.post(`${config.PROXY_URL}/proxy/register`, {
161
195
  path: config.PROXY_APP_PATH,
@@ -9,7 +9,7 @@ import { fileURLToPath } from 'url';
9
9
  import fs from 'fs';
10
10
  import { setupLogging } from './utils/logger.js';
11
11
  import { getConfig } from './utils/config-manager.js';
12
- import { getSharedFiles, validateSafePath } from './utils/file-manager.js';
12
+ import { getSharedFiles, validateSafePath, writeSharedFile, copyToSharedFolder, createDirectory, deleteDirectory, moveFile, } from './utils/file-manager.js';
13
13
  import { VERSION } from './version.js';
14
14
  const __filename = fileURLToPath(import.meta.url);
15
15
  const __dirname = path.dirname(__filename);
@@ -99,10 +99,15 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
99
99
  },
100
100
  {
101
101
  name: 'list_shared_files',
102
- description: 'List all files currently available in the shared folder.',
102
+ description: 'List files and directories in the shared folder. Use the optional path parameter to navigate into a subdirectory.',
103
103
  inputSchema: {
104
104
  type: 'object',
105
- properties: {},
105
+ properties: {
106
+ path: {
107
+ type: 'string',
108
+ description: 'Relative path of the subdirectory to list (e.g. "docs/reports"). Defaults to the root shared folder.',
109
+ },
110
+ },
106
111
  },
107
112
  },
108
113
  {
@@ -122,15 +127,105 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
122
127
  {
123
128
  name: 'preview_file',
124
129
  description: 'Preview the content of a .txt or .md (Markdown) file from the shared folder. Returns the raw text content (max 1 MB).',
130
+ inputSchema: {
131
+ type: 'object',
132
+ properties: {
133
+ path: {
134
+ type: 'string',
135
+ description: 'Relative path to the file in the shared folder (e.g. "docs/notes.md").',
136
+ },
137
+ },
138
+ required: ['path'],
139
+ },
140
+ },
141
+ {
142
+ name: 'write_file',
143
+ description: 'Write text content to a new file in the shared folder, making it immediately available to the user.',
125
144
  inputSchema: {
126
145
  type: 'object',
127
146
  properties: {
128
147
  filename: {
129
148
  type: 'string',
130
- description: 'The name of the file to preview (filename only, not the full path).',
149
+ description: 'Name of the file to create (e.g. "report.md"). Must not contain path separators.',
150
+ },
151
+ content: {
152
+ type: 'string',
153
+ description: 'Text content to write into the file.',
154
+ },
155
+ path: {
156
+ type: 'string',
157
+ description: 'Target subdirectory relative to the shared folder (e.g. "docs/reports"). The directory is created if it does not exist.',
131
158
  },
132
159
  },
133
- required: ['filename'],
160
+ required: ['filename', 'content'],
161
+ },
162
+ },
163
+ {
164
+ name: 'copy_file',
165
+ description: 'Copy an existing file from the agent workspace to the shared folder, making it available to the user.',
166
+ inputSchema: {
167
+ type: 'object',
168
+ properties: {
169
+ source_path: {
170
+ type: 'string',
171
+ description: 'Absolute path to the source file in the agent workspace.',
172
+ },
173
+ filename: {
174
+ type: 'string',
175
+ description: 'Optional target filename in the shared folder. Defaults to the source file basename.',
176
+ },
177
+ path: {
178
+ type: 'string',
179
+ description: 'Target subdirectory relative to the shared folder (e.g. "exports"). The directory is created if it does not exist.',
180
+ },
181
+ },
182
+ required: ['source_path'],
183
+ },
184
+ },
185
+ {
186
+ name: 'create_directory',
187
+ description: 'Create a new subdirectory inside the shared folder.',
188
+ inputSchema: {
189
+ type: 'object',
190
+ properties: {
191
+ path: {
192
+ type: 'string',
193
+ description: 'Relative path of the directory to create (e.g. "docs/reports"). Parent directories are created automatically.',
194
+ },
195
+ },
196
+ required: ['path'],
197
+ },
198
+ },
199
+ {
200
+ name: 'delete_directory',
201
+ description: 'Delete an empty subdirectory from the shared folder. Fails if the directory contains files or subdirectories.',
202
+ inputSchema: {
203
+ type: 'object',
204
+ properties: {
205
+ path: {
206
+ type: 'string',
207
+ description: 'Relative path of the directory to delete (e.g. "docs/drafts").',
208
+ },
209
+ },
210
+ required: ['path'],
211
+ },
212
+ },
213
+ {
214
+ name: 'move_file',
215
+ description: 'Move or rename a file within the shared folder.',
216
+ inputSchema: {
217
+ type: 'object',
218
+ properties: {
219
+ from: {
220
+ type: 'string',
221
+ description: 'Relative path of the source file (e.g. "report.md" or "docs/draft.md").',
222
+ },
223
+ to: {
224
+ type: 'string',
225
+ description: 'Relative path of the destination (e.g. "archive/report.md").',
226
+ },
227
+ },
228
+ required: ['from', 'to'],
134
229
  },
135
230
  },
136
231
  ],
@@ -147,15 +242,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
147
242
  };
148
243
  }
149
244
  case 'list_shared_files': {
150
- const files = getSharedFiles();
151
- if (files.length === 0) {
245
+ const { path: relativePath } = z.object({ path: z.string().optional() }).parse(args);
246
+ const entries = getSharedFiles(relativePath ?? '');
247
+ if (entries.length === 0) {
248
+ const location = relativePath ? `"${relativePath}"` : 'the shared folder';
152
249
  return {
153
- content: [{ type: 'text', text: 'The shared folder is empty.' }],
250
+ content: [{ type: 'text', text: `${location} is empty.` }],
154
251
  };
155
252
  }
156
- const fileList = files.map(f => `- ${f.name} (${(f.size / 1024).toFixed(2)} KB)`).join('\n');
253
+ const dirs = entries.filter(e => e.type === 'directory').map(e => `📁 ${e.name}/`);
254
+ const files = entries.filter(e => e.type === 'file').map(e => `📄 ${e.name} (${((e.size ?? 0) / 1024).toFixed(2)} KB)`);
255
+ const list = [...dirs, ...files].join('\n');
256
+ const location = relativePath ? `"${relativePath}"` : 'shared folder';
157
257
  return {
158
- content: [{ type: 'text', text: `Files in shared folder:\n${fileList}` }],
258
+ content: [{ type: 'text', text: `Contents of ${location}:\n${list}` }],
159
259
  };
160
260
  }
161
261
  case 'share_file': {
@@ -165,18 +265,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
165
265
  };
166
266
  }
167
267
  case 'preview_file': {
168
- const { filename } = z.object({ filename: z.string() }).parse(args);
169
- const ext = path.extname(filename).toLowerCase();
268
+ const { path: relativePath } = z.object({ path: z.string() }).parse(args);
269
+ const ext = path.extname(relativePath).toLowerCase();
170
270
  if (!['.txt', '.md'].includes(ext)) {
171
271
  return {
172
272
  content: [{ type: 'text', text: `Error: Preview is only supported for .txt and .md files.` }],
173
273
  isError: true,
174
274
  };
175
275
  }
176
- const safePath = validateSafePath(filename);
276
+ const safePath = validateSafePath(relativePath);
177
277
  if (!fs.existsSync(safePath)) {
178
278
  return {
179
- content: [{ type: 'text', text: `Error: File "${filename}" not found in shared folder.` }],
279
+ content: [{ type: 'text', text: `Error: File "${relativePath}" not found in shared folder.` }],
180
280
  isError: true,
181
281
  };
182
282
  }
@@ -192,6 +292,49 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
192
292
  content: [{ type: 'text', text: content }],
193
293
  };
194
294
  }
295
+ case 'write_file': {
296
+ const { filename, content, path: relativePath } = z.object({
297
+ filename: z.string(),
298
+ content: z.string(),
299
+ path: z.string().optional(),
300
+ }).parse(args);
301
+ const dest = writeSharedFile(filename, content, relativePath ?? '');
302
+ return {
303
+ content: [{ type: 'text', text: `File "${path.basename(dest)}" written to shared folder${relativePath ? ` (${relativePath})` : ''}.` }],
304
+ };
305
+ }
306
+ case 'copy_file': {
307
+ const { source_path, filename, path: relativePath } = z.object({
308
+ source_path: z.string(),
309
+ filename: z.string().optional(),
310
+ path: z.string().optional(),
311
+ }).parse(args);
312
+ const copiedName = copyToSharedFolder(source_path, filename, relativePath ?? '');
313
+ return {
314
+ content: [{ type: 'text', text: `File "${copiedName}" copied to shared folder.` }],
315
+ };
316
+ }
317
+ case 'create_directory': {
318
+ const { path: dirPath } = z.object({ path: z.string() }).parse(args);
319
+ createDirectory(dirPath);
320
+ return {
321
+ content: [{ type: 'text', text: `Directory "${dirPath}" created successfully.` }],
322
+ };
323
+ }
324
+ case 'delete_directory': {
325
+ const { path: dirPath } = z.object({ path: z.string() }).parse(args);
326
+ deleteDirectory(dirPath);
327
+ return {
328
+ content: [{ type: 'text', text: `Directory "${dirPath}" deleted successfully.` }],
329
+ };
330
+ }
331
+ case 'move_file': {
332
+ const { from, to } = z.object({ from: z.string(), to: z.string() }).parse(args);
333
+ moveFile(from, to);
334
+ return {
335
+ content: [{ type: 'text', text: `File moved from "${from}" to "${to}".` }],
336
+ };
337
+ }
195
338
  default:
196
339
  throw new Error(`Tool not found: ${name}`);
197
340
  }
@@ -1,39 +1,83 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { getConfig } from './config-manager.js';
4
- export function getSharedFiles() {
4
+ export function validateSafePath(relativePath) {
5
5
  const { shared_folder_path } = getConfig();
6
- if (!fs.existsSync(shared_folder_path)) {
7
- fs.mkdirSync(shared_folder_path, { recursive: true });
6
+ const resolvedBase = path.resolve(shared_folder_path);
7
+ const fullPath = path.resolve(shared_folder_path, relativePath);
8
+ if (fullPath !== resolvedBase && !fullPath.startsWith(resolvedBase + path.sep)) {
9
+ throw new Error('Path traversal detected');
10
+ }
11
+ return fullPath;
12
+ }
13
+ export function getSharedFiles(relativePath = '') {
14
+ const dirPath = validateSafePath(relativePath);
15
+ if (!fs.existsSync(dirPath)) {
16
+ fs.mkdirSync(dirPath, { recursive: true });
8
17
  return [];
9
18
  }
10
- return fs.readdirSync(shared_folder_path).filter(file => {
11
- const filePath = path.join(shared_folder_path, file);
12
- return fs.statSync(filePath).isFile();
13
- }).map(file => {
14
- const filePath = path.join(shared_folder_path, file);
15
- const stats = fs.statSync(filePath);
19
+ return fs.readdirSync(dirPath).map(name => {
20
+ const fullPath = path.join(dirPath, name);
21
+ const stats = fs.statSync(fullPath);
22
+ if (stats.isDirectory()) {
23
+ return { name, type: 'directory', mtime: stats.mtime };
24
+ }
16
25
  return {
17
- name: file,
26
+ name,
27
+ type: 'file',
18
28
  size: stats.size,
19
29
  mtime: stats.mtime,
20
- extension: path.extname(file).toLowerCase(),
30
+ extension: path.extname(name).toLowerCase(),
21
31
  };
32
+ }).sort((a, b) => {
33
+ if (a.type !== b.type)
34
+ return a.type === 'directory' ? -1 : 1;
35
+ return a.name.localeCompare(b.name);
22
36
  });
23
37
  }
24
- export function validateSafePath(filename) {
25
- const { shared_folder_path } = getConfig();
26
- const safeName = path.basename(filename);
27
- const fullPath = path.join(shared_folder_path, safeName);
28
- // Extra safety check
29
- if (!fullPath.startsWith(path.resolve(shared_folder_path))) {
30
- throw new Error('Path traversal detected');
31
- }
32
- return fullPath;
33
- }
34
- export function deleteFile(filename) {
35
- const fullPath = validateSafePath(filename);
38
+ export function deleteFile(relativePath) {
39
+ const fullPath = validateSafePath(relativePath);
36
40
  if (fs.existsSync(fullPath)) {
37
41
  fs.unlinkSync(fullPath);
38
42
  }
39
43
  }
44
+ export function writeSharedFile(filename, content, relativePath = '') {
45
+ const dir = validateSafePath(relativePath);
46
+ fs.mkdirSync(dir, { recursive: true });
47
+ const dest = path.join(dir, path.basename(filename));
48
+ fs.writeFileSync(dest, content, 'utf-8');
49
+ return dest;
50
+ }
51
+ export function copyToSharedFolder(sourcePath, filename, relativePath = '') {
52
+ const targetFilename = filename ? path.basename(filename) : path.basename(sourcePath);
53
+ const stats = fs.statSync(sourcePath);
54
+ if (!stats.isFile()) {
55
+ throw new Error(`Source path is not a file: ${sourcePath}`);
56
+ }
57
+ const dir = validateSafePath(relativePath);
58
+ fs.mkdirSync(dir, { recursive: true });
59
+ fs.copyFileSync(sourcePath, path.join(dir, targetFilename));
60
+ return relativePath ? `${relativePath}/${targetFilename}` : targetFilename;
61
+ }
62
+ export function createDirectory(relativePath) {
63
+ const fullPath = validateSafePath(relativePath);
64
+ fs.mkdirSync(fullPath, { recursive: true });
65
+ }
66
+ export function deleteDirectory(relativePath) {
67
+ const fullPath = validateSafePath(relativePath);
68
+ if (!fs.existsSync(fullPath))
69
+ throw new Error('Directory not found');
70
+ const contents = fs.readdirSync(fullPath);
71
+ if (contents.length > 0)
72
+ throw new Error('Directory is not empty');
73
+ fs.rmdirSync(fullPath);
74
+ }
75
+ export function moveFile(fromRelative, toRelative) {
76
+ const fromPath = validateSafePath(fromRelative);
77
+ const toPath = validateSafePath(toRelative);
78
+ if (!fs.existsSync(fromPath)) {
79
+ throw new Error(`File not found: ${fromRelative}`);
80
+ }
81
+ fs.mkdirSync(path.dirname(toPath), { recursive: true });
82
+ fs.renameSync(fromPath, toPath);
83
+ }
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '1.0.1';
1
+ export const VERSION = '1.0.3';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "file-share-mcp",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "MCP server for bidirectional file sharing between AI agents and users, with a built-in web GUI",
5
5
  "author": "",
6
6
  "license": "ISC",
@@ -0,0 +1 @@
1
+ @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-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-divide-y-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-leading:initial;--tw-font-weight:initial;--tw-tracking: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-duration:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@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-200:oklch(88.5% .062 18.334);--color-red-400:oklch(70.4% .191 22.216);--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-900:oklch(39.6% .141 25.723);--color-yellow-400:oklch(85.2% .199 91.936);--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-blue-300:oklch(80.9% .105 251.813);--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-blue-900:oklch(37.9% .146 265.522);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-purple-600:oklch(55.8% .288 302.321);--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;--container-sm:24rem;--container-md:28rem;--container-4xl:56rem;--container-6xl:72rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--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-3xl:1.875rem;--text-3xl--line-height: 1.2 ;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wide:.025em;--leading-relaxed:1.625;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@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;-o-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}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}::-moz-placeholder{opacity:1}::placeholder{opacity:1}@supports (not (-webkit-appearance:-apple-pay-button)) or (contain-intrinsic-size:1px){::-moz-placeholder{color:currentColor}::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::-moz-placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}::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}::-webkit-calendar-picker-indicator{line-height:1}:-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{.fixed{position:fixed}.inset-0{inset:calc(var(--spacing)*0)}.z-40{z-index:40}.z-50{z-index:50}.mx-auto{margin-inline:auto}.-mt-0\.5{margin-top:calc(var(--spacing)*-.5)}.-mt-4{margin-top:calc(var(--spacing)*-4)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-8{margin-top:calc(var(--spacing)*8)}.mr-1{margin-right:calc(var(--spacing)*1)}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.mb-8{margin-bottom:calc(var(--spacing)*8)}.ml-2{margin-left:calc(var(--spacing)*2)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.h-3{height:calc(var(--spacing)*3)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-10{height:calc(var(--spacing)*10)}.h-12{height:calc(var(--spacing)*12)}.h-full{height:100%}.max-h-52{max-height:calc(var(--spacing)*52)}.max-h-\[85vh\]{max-height:85vh}.max-h-\[500px\]{max-height:500px}.min-h-\[200px\]{min-height:200px}.min-h-screen{min-height:100vh}.w-3{width:calc(var(--spacing)*3)}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-10{width:calc(var(--spacing)*10)}.w-12{width:calc(var(--spacing)*12)}.w-full{width:100%}.max-w-4xl{max-width:var(--container-4xl)}.max-w-6xl{max-width:var(--container-6xl)}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.scale-\[1\.02\]{scale:1.02}.rotate-90{rotate:90deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-spin{animation:var(--animate-spin)}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.grid-cols-1{grid-template-columns:repeat(1,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-1{gap:calc(var(--spacing)*1)}.gap-1\.5{gap:calc(var(--spacing)*1.5)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-6{gap:calc(var(--spacing)*6)}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-gray-700>:not(:last-child)){border-color:var(--color-gray-700)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-blue-400{border-color:var(--color-blue-400)}.border-blue-500{border-color:var(--color-blue-500)}.border-blue-500\/50{border-color:#3080ff80}@supports (color:color-mix(in lab,red,red)){.border-blue-500\/50{border-color:color-mix(in oklab,var(--color-blue-500)50%,transparent)}}.border-gray-600{border-color:var(--color-gray-600)}.border-gray-700{border-color:var(--color-gray-700)}.border-gray-700\/60{border-color:#36415399}@supports (color:color-mix(in lab,red,red)){.border-gray-700\/60{border-color:color-mix(in oklab,var(--color-gray-700)60%,transparent)}}.border-red-500{border-color:var(--color-red-500)}.bg-black\/60{background-color:#0009}@supports (color:color-mix(in lab,red,red)){.bg-black\/60{background-color:color-mix(in oklab,var(--color-black)60%,transparent)}}.bg-black\/70{background-color:#000000b3}@supports (color:color-mix(in lab,red,red)){.bg-black\/70{background-color:color-mix(in oklab,var(--color-black)70%,transparent)}}.bg-blue-500\/10{background-color:#3080ff1a}@supports (color:color-mix(in lab,red,red)){.bg-blue-500\/10{background-color:color-mix(in oklab,var(--color-blue-500)10%,transparent)}}.bg-blue-500\/15{background-color:#3080ff26}@supports (color:color-mix(in lab,red,red)){.bg-blue-500\/15{background-color:color-mix(in oklab,var(--color-blue-500)15%,transparent)}}.bg-blue-600{background-color:var(--color-blue-600)}.bg-blue-900\/40{background-color:#1c398e66}@supports (color:color-mix(in lab,red,red)){.bg-blue-900\/40{background-color:color-mix(in oklab,var(--color-blue-900)40%,transparent)}}.bg-gray-700{background-color:var(--color-gray-700)}.bg-gray-800{background-color:var(--color-gray-800)}.bg-gray-800\/50{background-color:#1e293980}@supports (color:color-mix(in lab,red,red)){.bg-gray-800\/50{background-color:color-mix(in oklab,var(--color-gray-800)50%,transparent)}}.bg-gray-900{background-color:var(--color-gray-900)}.bg-red-500\/15{background-color:#fb2c3626}@supports (color:color-mix(in lab,red,red)){.bg-red-500\/15{background-color:color-mix(in oklab,var(--color-red-500)15%,transparent)}}.bg-red-600{background-color:var(--color-red-600)}.bg-red-900\/30{background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.bg-red-900\/30{background-color:color-mix(in oklab,var(--color-red-900)30%,transparent)}}.bg-yellow-500\/15{background-color:#edb20026}@supports (color:color-mix(in lab,red,red)){.bg-yellow-500\/15{background-color:color-mix(in oklab,var(--color-yellow-500)15%,transparent)}}.bg-yellow-600{background-color:var(--color-yellow-600)}.bg-gradient-to-r{--tw-gradient-position:to right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-blue-400{--tw-gradient-from:var(--color-blue-400);--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))}.to-purple-500{--tw-gradient-to:var(--color-purple-500);--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))}.bg-clip-text{-webkit-background-clip:text;background-clip:text}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-2{padding:calc(var(--spacing)*2)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.p-8{padding:calc(var(--spacing)*8)}.p-12{padding:calc(var(--spacing)*12)}.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-0\.5{padding-block:calc(var(--spacing)*.5)}.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)}.pl-2{padding-left:calc(var(--spacing)*2)}.pl-5{padding-left:calc(var(--spacing)*5)}.text-center{text-align:center}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--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))}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.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)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.break-words{overflow-wrap:break-word}.whitespace-pre-wrap{white-space:pre-wrap}.text-blue-400{color:var(--color-blue-400)}.text-gray-100{color:var(--color-gray-100)}.text-gray-200{color:var(--color-gray-200)}.text-gray-300{color:var(--color-gray-300)}.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-purple-400{color:var(--color-purple-400)}.text-red-200{color:var(--color-red-200)}.text-red-400{color:var(--color-red-400)}.text-transparent{color:#0000}.text-white{color:var(--color-white)}.text-yellow-400{color:var(--color-yellow-400)}.text-yellow-500\/80{color:#edb200cc}@supports (color:color-mix(in lab,red,red)){.text-yellow-500\/80{color:color-mix(in oklab,var(--color-yellow-500)80%,transparent)}}.uppercase{text-transform:uppercase}.italic{font-style:italic}.opacity-40{opacity:.4}.opacity-50{opacity:.5}.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-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px 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)}.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))}.transition-transform{transition-property:transform,translate,scale,rotate;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}@media(hover:hover){.group-hover\:scale-110:is(:where(.group):hover *){--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x)var(--tw-scale-y)}.group-hover\:text-blue-400:is(:where(.group):hover *){color:var(--color-blue-400)}.hover\:border-blue-500:hover{border-color:var(--color-blue-500)}.hover\:bg-blue-500\/5:hover{background-color:#3080ff0d}@supports (color:color-mix(in lab,red,red)){.hover\:bg-blue-500\/5:hover{background-color:color-mix(in oklab,var(--color-blue-500)5%,transparent)}}.hover\:bg-blue-600:hover{background-color:var(--color-blue-600)}.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-gray-600:hover{background-color:var(--color-gray-600)}.hover\:bg-gray-700:hover{background-color:var(--color-gray-700)}.hover\:bg-gray-700\/40:hover{background-color:#36415366}@supports (color:color-mix(in lab,red,red)){.hover\:bg-gray-700\/40:hover{background-color:color-mix(in oklab,var(--color-gray-700)40%,transparent)}}.hover\:bg-purple-600:hover{background-color:var(--color-purple-600)}.hover\:bg-red-600:hover{background-color:var(--color-red-600)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:bg-yellow-600:hover{background-color:var(--color-yellow-600)}.hover\:bg-yellow-700:hover{background-color:var(--color-yellow-700)}.hover\:text-blue-300:hover{color:var(--color-blue-300)}.hover\:text-blue-400:hover{color:var(--color-blue-400)}.hover\:text-gray-100:hover{color:var(--color-gray-100)}.hover\:text-white:hover{color:var(--color-white)}}.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-500:focus{--tw-ring-color:var(--color-blue-500)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media(min-width:48rem){.md\:col-span-1{grid-column:span 1/span 1}.md\:col-span-2{grid-column:span 2/span 2}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}}:root{color-scheme:light dark;color:#ffffffde;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-color:#242424;font-family:Inter,system-ui,Avenir,Helvetica,Arial,sans-serif;font-weight:400;line-height:1.5}body{min-width:320px;min-height:100vh;margin:0;display:flex}#root{width:100%}.markdown-preview{color:#e2e8f0;font-size:.9rem;line-height:1.75}.markdown-preview h1,.markdown-preview h2,.markdown-preview h3,.markdown-preview h4,.markdown-preview h5,.markdown-preview h6{color:#f1f5f9;margin-top:1.5em;margin-bottom:.5em;font-weight:600;line-height:1.3}.markdown-preview h1{border-bottom:1px solid #334155;padding-bottom:.3em;font-size:1.75rem}.markdown-preview h2{border-bottom:1px solid #1e293b;padding-bottom:.2em;font-size:1.35rem}.markdown-preview h3{font-size:1.1rem}.markdown-preview p{margin-top:.75em;margin-bottom:.75em}.markdown-preview a{color:#60a5fa;text-decoration:underline}.markdown-preview a:hover{color:#93c5fd}.markdown-preview strong{color:#f8fafc;font-weight:700}.markdown-preview em{font-style:italic}.markdown-preview code{color:#a5f3fc;background:#1e293b;border-radius:4px;padding:.15em .4em;font-family:JetBrains Mono,Fira Code,monospace;font-size:.85em}.markdown-preview pre{background:#0f172a;border:1px solid #334155;border-radius:8px;margin:1em 0;padding:1em 1.25em;overflow-x:auto}.markdown-preview pre code{color:#e2e8f0;background:0 0;padding:0;font-size:.85em}.markdown-preview blockquote{color:#94a3b8;border-left:3px solid #6366f1;margin-left:0;padding-left:1em;font-style:italic}.markdown-preview ul,.markdown-preview ol{margin:.75em 0;padding-left:1.5em}.markdown-preview li{margin:.3em 0}.markdown-preview table{border-collapse:collapse;width:100%;margin:1em 0;font-size:.875em}.markdown-preview th,.markdown-preview td{text-align:left;border:1px solid #334155;padding:.5em .75em}.markdown-preview th{background:#1e293b;font-weight:600}.markdown-preview tr:nth-child(2n){background:#0f172a}.markdown-preview hr{border:none;border-top:1px solid #334155;margin:1.5em 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-divide-y-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-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{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-duration{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@keyframes spin{to{transform:rotate(360deg)}}