aicodeswitch 1.1.2 → 1.2.1

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/bin/update.js CHANGED
@@ -2,16 +2,17 @@ const path = require('path');
2
2
  const fs = require('fs');
3
3
  const os = require('os');
4
4
  const https = require('https');
5
+ const http = require('http');
5
6
  const { spawn } = require('child_process');
6
7
  const chalk = require('chalk');
7
8
  const ora = require('ora');
8
9
  const boxen = require('boxen');
10
+ const tar = require('tar');
9
11
 
10
12
  const AICOSWITCH_DIR = path.join(os.homedir(), '.aicodeswitch');
11
13
  const RELEASES_DIR = path.join(AICOSWITCH_DIR, 'releases');
12
14
  const CURRENT_FILE = path.join(AICOSWITCH_DIR, 'current');
13
15
  const PACKAGE_NAME = 'aicodeswitch';
14
- const NPM_REGISTRY = 'https://registry.npmjs.org';
15
16
 
16
17
  // 确保目录存在
17
18
  const ensureDir = (dirPath) => {
@@ -96,40 +97,67 @@ const getLatestVersion = () => {
96
97
  });
97
98
  };
98
99
 
99
- // 使用 npm 安装指定版本到指定目录
100
- const installPackage = (version, targetDir) => {
100
+ // 下载 tarball 文件
101
+ const downloadTarball = (url, destPath) => {
101
102
  return new Promise((resolve, reject) => {
102
- const npmProcess = spawn('npm', [
103
- 'install',
104
- `${PACKAGE_NAME}@${version}`,
105
- '--prefix',
106
- targetDir,
107
- '--no-save',
108
- '--no-package-lock',
109
- '--no-bin-links'
110
- ]);
111
-
112
- let stderr = '';
113
-
114
- npmProcess.stderr.on('data', (data) => {
115
- stderr += data.toString();
103
+ const urlObj = new URL(url);
104
+ const protocol = urlObj.protocol === 'http:' ? http : https;
105
+
106
+ const requestOptions = {
107
+ hostname: urlObj.hostname,
108
+ port: urlObj.port,
109
+ path: urlObj.pathname + urlObj.search,
110
+ method: 'GET',
111
+ headers: {
112
+ 'User-Agent': 'aicodeswitch'
113
+ }
114
+ };
115
+
116
+ const req = protocol.request(requestOptions, (res) => {
117
+ if (res.statusCode !== 200) {
118
+ reject(new Error(`Failed to download: HTTP ${res.statusCode}`));
119
+ return;
120
+ }
121
+
122
+ const fileStream = fs.createWriteStream(destPath);
123
+ res.pipe(fileStream);
124
+
125
+ fileStream.on('finish', () => {
126
+ fileStream.close();
127
+ resolve(destPath);
128
+ });
129
+
130
+ fileStream.on('error', (err) => {
131
+ fs.unlink(destPath, () => {});
132
+ reject(err);
133
+ });
116
134
  });
117
135
 
118
- npmProcess.on('close', (code) => {
119
- if (code === 0) {
120
- // npm install 会把包安装到 targetDir/node_modules/ 目录下
121
- const packageDir = path.join(targetDir, 'node_modules', PACKAGE_NAME);
122
- if (fs.existsSync(packageDir)) {
123
- resolve(packageDir);
124
- } else {
125
- reject(new Error('Package installation directory not found'));
126
- }
127
- } else {
128
- reject(new Error(`npm install failed: ${stderr}`));
136
+ req.on('error', (err) => {
137
+ if (fs.existsSync(destPath)) {
138
+ fs.unlink(destPath, () => {});
129
139
  }
140
+ reject(err);
130
141
  });
131
142
 
132
- npmProcess.on('error', reject);
143
+ req.setTimeout(60000, () => {
144
+ req.destroy();
145
+ if (fs.existsSync(destPath)) {
146
+ fs.unlink(destPath, () => {});
147
+ }
148
+ reject(new Error('Download timeout'));
149
+ });
150
+
151
+ req.end();
152
+ });
153
+ };
154
+
155
+ // 解压 tarball 到指定目录
156
+ const extractTarball = (tarballPath, destDir) => {
157
+ return tar.x({
158
+ file: tarballPath,
159
+ cwd: destDir,
160
+ strip: 1, // 去掉 package 目录层级
133
161
  });
134
162
  };
135
163
 
@@ -159,6 +187,37 @@ const restart = () => {
159
187
  });
160
188
  };
161
189
 
190
+ // 清理旧版本的下载文件(保留最近 3 个版本)
191
+ const cleanupOldVersions = () => {
192
+ try {
193
+ if (!fs.existsSync(RELEASES_DIR)) {
194
+ return;
195
+ }
196
+
197
+ const versions = fs.readdirSync(RELEASES_DIR)
198
+ .filter(item => {
199
+ const itemPath = path.join(RELEASES_DIR, item);
200
+ return fs.statSync(itemPath).isDirectory();
201
+ })
202
+ .sort((a, b) => {
203
+ // 按版本号降序排序
204
+ return compareVersions(b, a);
205
+ });
206
+
207
+ // 保留最近 3 个版本,删除其他版本
208
+ if (versions.length > 3) {
209
+ const versionsToDelete = versions.slice(3);
210
+ versionsToDelete.forEach(version => {
211
+ const versionPath = path.join(RELEASES_DIR, version);
212
+ fs.rmSync(versionPath, { recursive: true, force: true });
213
+ });
214
+ }
215
+ } catch (err) {
216
+ // 清理失败不影响更新流程
217
+ console.error(chalk.yellow(`Warning: Failed to cleanup old versions: ${err.message}`));
218
+ }
219
+ };
220
+
162
221
  // 主更新逻辑
163
222
  const update = async () => {
164
223
  console.log('\n');
@@ -182,7 +241,6 @@ const update = async () => {
182
241
  if (comparison <= 0) {
183
242
  console.log(chalk.yellow(`\n✓ You are already on the latest version (${chalk.bold(currentVersion)})\n`));
184
243
  process.exit(0);
185
- return;
186
244
  }
187
245
 
188
246
  console.log(chalk.cyan(`\n📦 Update available: ${chalk.bold(currentVersion)} → ${chalk.bold(latestVersion)}\n`));
@@ -190,34 +248,58 @@ const update = async () => {
190
248
  // 确保目录存在
191
249
  ensureDir(RELEASES_DIR);
192
250
 
193
- // 安装新版本
194
- const installSpinner = ora({
195
- text: chalk.cyan('Downloading and installing from npm...'),
251
+ // 创建版本目录
252
+ const versionDir = path.join(RELEASES_DIR, latestVersion);
253
+ ensureDir(versionDir);
254
+
255
+ // 下载 tarball
256
+ const downloadSpinner = ora({
257
+ text: chalk.cyan('Downloading from npm...'),
196
258
  color: 'cyan'
197
259
  }).start();
198
260
 
199
- const versionDir = path.join(RELEASES_DIR, latestVersion);
200
- ensureDir(versionDir);
261
+ const tarballPath = path.join(versionDir, 'package.tgz');
201
262
 
202
263
  try {
203
- const packageDir = await installPackage(latestVersion, versionDir);
204
- installSpinner.succeed(chalk.green('Package installed'));
264
+ await downloadTarball(latestInfo.tarball, tarballPath);
265
+ downloadSpinner.succeed(chalk.green('Download completed'));
205
266
  } catch (err) {
206
- installSpinner.fail(chalk.red('Installation failed'));
267
+ downloadSpinner.fail(chalk.red('Download failed'));
207
268
  console.log(chalk.red(`Error: ${err.message}\n`));
208
269
  process.exit(1);
209
- return;
210
270
  }
211
271
 
212
- // 实际的包在 node_modules/aicodeswitch 目录下
213
- const actualPackageDir = path.join(versionDir, 'node_modules', PACKAGE_NAME);
214
- updateCurrentFile(actualPackageDir);
272
+ // 解压 tarball
273
+ const extractSpinner = ora({
274
+ text: chalk.cyan('Extracting package...'),
275
+ color: 'cyan'
276
+ }).start();
277
+
278
+ try {
279
+ await extractTarball(tarballPath, versionDir);
280
+ extractSpinner.succeed(chalk.green('Package extracted'));
281
+ } catch (err) {
282
+ extractSpinner.fail(chalk.red('Extraction failed'));
283
+ console.log(chalk.red(`Error: ${err.message}\n`));
284
+ process.exit(1);
285
+ } finally {
286
+ // 删除 tarball 文件
287
+ if (fs.existsSync(tarballPath)) {
288
+ fs.unlinkSync(tarballPath);
289
+ }
290
+ }
291
+
292
+ // 更新 current 文件
293
+ updateCurrentFile(versionDir);
294
+
295
+ // 清理旧版本
296
+ cleanupOldVersions();
215
297
 
216
298
  // 显示更新成功信息
217
299
  console.log(boxen(
218
300
  chalk.green.bold('✨ Update Successful!\n\n') +
219
301
  chalk.white('Version: ') + chalk.cyan.bold(latestVersion) + '\n' +
220
- chalk.white('Location: ') + chalk.gray(actualPackageDir) + '\n\n' +
302
+ chalk.white('Location: ') + chalk.gray(versionDir) + '\n\n' +
221
303
  chalk.gray('Restarting server with the new version...'),
222
304
  {
223
305
  padding: 1,
@@ -234,7 +316,6 @@ const update = async () => {
234
316
  console.log(chalk.yellow(`\n⚠️ Update completed, but restart failed: ${err.message}`));
235
317
  console.log(chalk.cyan('Please manually run: ') + chalk.yellow('aicos restart\n'));
236
318
  process.exit(1);
237
- return;
238
319
  }
239
320
 
240
321
  process.exit(0);
@@ -292,19 +292,13 @@ class DatabaseManager {
292
292
  getLogs() {
293
293
  return __awaiter(this, arguments, void 0, function* (limit = 100, offset = 0) {
294
294
  var _a, e_1, _b, _c;
295
- const logs = [];
296
- let count = 0;
295
+ const allLogs = [];
297
296
  try {
298
- for (var _d = true, _e = __asyncValues(this.logDb.iterator({ reverse: true })), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
297
+ for (var _d = true, _e = __asyncValues(this.logDb.iterator()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
299
298
  _c = _f.value;
300
299
  _d = false;
301
300
  const [, value] = _c;
302
- if (count >= offset && logs.length < limit) {
303
- logs.push(JSON.parse(value));
304
- }
305
- count++;
306
- if (logs.length >= limit)
307
- break;
301
+ allLogs.push(JSON.parse(value));
308
302
  }
309
303
  }
310
304
  catch (e_1_1) { e_1 = { error: e_1_1 }; }
@@ -314,7 +308,10 @@ class DatabaseManager {
314
308
  }
315
309
  finally { if (e_1) throw e_1.error; }
316
310
  }
317
- return logs;
311
+ // Sort by timestamp in descending order (newest first)
312
+ allLogs.sort((a, b) => b.timestamp - a.timestamp);
313
+ // Apply offset and limit
314
+ return allLogs.slice(offset, offset + limit);
318
315
  });
319
316
  }
320
317
  clearLogs() {
@@ -340,19 +337,13 @@ class DatabaseManager {
340
337
  getAccessLogs() {
341
338
  return __awaiter(this, arguments, void 0, function* (limit = 100, offset = 0) {
342
339
  var _a, e_2, _b, _c;
343
- const logs = [];
344
- let count = 0;
340
+ const allLogs = [];
345
341
  try {
346
- for (var _d = true, _e = __asyncValues(this.accessLogDb.iterator({ reverse: true })), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
342
+ for (var _d = true, _e = __asyncValues(this.accessLogDb.iterator()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
347
343
  _c = _f.value;
348
344
  _d = false;
349
345
  const [, value] = _c;
350
- if (count >= offset && logs.length < limit) {
351
- logs.push(JSON.parse(value));
352
- }
353
- count++;
354
- if (logs.length >= limit)
355
- break;
346
+ allLogs.push(JSON.parse(value));
356
347
  }
357
348
  }
358
349
  catch (e_2_1) { e_2 = { error: e_2_1 }; }
@@ -362,7 +353,10 @@ class DatabaseManager {
362
353
  }
363
354
  finally { if (e_2) throw e_2.error; }
364
355
  }
365
- return logs;
356
+ // Sort by timestamp in descending order (newest first)
357
+ allLogs.sort((a, b) => b.timestamp - a.timestamp);
358
+ // Apply offset and limit
359
+ return allLogs.slice(offset, offset + limit);
366
360
  });
367
361
  }
368
362
  clearAccessLogs() {
@@ -380,19 +374,13 @@ class DatabaseManager {
380
374
  getErrorLogs() {
381
375
  return __awaiter(this, arguments, void 0, function* (limit = 100, offset = 0) {
382
376
  var _a, e_3, _b, _c;
383
- const logs = [];
384
- let count = 0;
377
+ const allLogs = [];
385
378
  try {
386
- for (var _d = true, _e = __asyncValues(this.errorLogDb.iterator({ reverse: true })), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
379
+ for (var _d = true, _e = __asyncValues(this.errorLogDb.iterator()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
387
380
  _c = _f.value;
388
381
  _d = false;
389
382
  const [, value] = _c;
390
- if (count >= offset && logs.length < limit) {
391
- logs.push(JSON.parse(value));
392
- }
393
- count++;
394
- if (logs.length >= limit)
395
- break;
383
+ allLogs.push(JSON.parse(value));
396
384
  }
397
385
  }
398
386
  catch (e_3_1) { e_3 = { error: e_3_1 }; }
@@ -402,7 +390,10 @@ class DatabaseManager {
402
390
  }
403
391
  finally { if (e_3) throw e_3.error; }
404
392
  }
405
- return logs;
393
+ // Sort by timestamp in descending order (newest first)
394
+ allLogs.sort((a, b) => b.timestamp - a.timestamp);
395
+ // Apply offset and limit
396
+ return allLogs.slice(offset, offset + limit);
406
397
  });
407
398
  }
408
399
  clearErrorLogs() {
@@ -1 +1 @@
1
- .app{display:flex;width:100%;height:100%}.sidebar{width:260px;background:var(--bg-sidebar);color:var(--text-primary);display:flex;flex-direction:column;padding:0 0 80px;border-radius:20px;box-shadow:0 8px 32px var(--shadow-primary),0 0 20px #0478574d;margin:16px;position:relative;z-index:1;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.1)}.logo{padding:28px 20px;border-bottom:2px solid rgba(255,255,255,.2);text-align:center;position:relative;overflow:hidden}.logo:before{content:"✨";position:absolute;top:12px;left:12px;font-size:16px;opacity:.7;transition:all .3s ease}.logo:after{content:"🌟";position:absolute;top:12px;right:12px;font-size:16px;opacity:.7;transition:all .3s ease}.logo:hover:before,.logo:focus:before{animation:sparkle 3s ease-in-out infinite}.logo:hover:after,.logo:focus:after{animation:sparkle 3s ease-in-out infinite 1.5s}@keyframes sparkle{0%,to{transform:scale(1) rotate(0);opacity:.7}50%{transform:scale(1.2) rotate(180deg);opacity:1}}.logo h2{font-size:22px;font-weight:800;background:linear-gradient(135deg,#fff,#f8f8ff,#e6e6fa);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;text-shadow:0 2px 4px rgba(0,0,0,.1),0 0 20px rgba(4,120,87,.3);position:relative;z-index:1;letter-spacing:1px;text-transform:uppercase;transition:all .3s ease}.logo:hover h2,.logo:focus h2{animation:logoGlow 4s ease-in-out infinite}@keyframes logoGlow{0%,to{text-shadow:0 2px 4px rgba(0,0,0,.1),0 0 20px rgba(4,120,87,.3)}50%{text-shadow:0 2px 4px rgba(0,0,0,.1),0 0 30px rgba(4,120,87,.5),0 0 40px rgba(4,120,87,.3)}}.nav-menu{list-style:none;padding:16px 0;margin:0}.nav-menu li{margin:8px 12px}.nav-menu a{display:block;padding:14px 20px;color:var(--text-sidebar);text-decoration:none;border-radius:12px;transition:all .3s ease-out;font-weight:500;position:relative;overflow:hidden}.nav-menu a:before{content:"";position:absolute;top:0;left:-100%;width:100%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.15),transparent);transition:left .5s ease-out}.nav-menu a:hover:before{left:100%}.nav-menu a:hover{background-color:#ffffff1a;transform:translate(4px);box-shadow:0 4px 12px #0f172a33}.nav-menu a.active{background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));box-shadow:0 4px 16px var(--shadow-primary)}.main-content{flex:1;padding:24px;overflow-y:auto;background:var(--bg-primary);position:relative;z-index:1;margin:16px 16px 16px 0;border-radius:20px}.main-content:before{content:"";position:absolute;top:0;left:0;right:0;bottom:0;background-image:radial-gradient(circle at 20% 80%,rgba(255,206,92,.1) 0%,transparent 50%),radial-gradient(circle at 80% 20%,rgba(134,204,202,.1) 0%,transparent 50%),radial-gradient(circle at 40% 40%,rgba(255,113,206,.05) 0%,transparent 50%);pointer-events:none}.page-header{margin-bottom:24px;position:relative;z-index:1}.page-header h1{font-family:Fredoka,sans-serif;font-size:28px;font-weight:700;color:var(--accent-primary);margin-bottom:8px;text-shadow:0 2px 4px var(--shadow-primary)}.page-header p{color:var(--text-muted);font-size:16px;font-weight:400}.card{background:var(--bg-card);border-radius:20px;padding:24px;box-shadow:0 8px 32px var(--shadow-secondary),0 0 16px #0478571a;margin-bottom:24px;border:1px solid var(--border-primary);position:relative;overflow:hidden;transition:all .3s ease-out;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.card:before{content:"";position:absolute;top:0;left:0;right:0;height:4px;background:linear-gradient(90deg,var(--accent-primary),var(--accent-secondary),var(--accent-primary));border-radius:20px 20px 0 0;background-size:200% 100%;transition:all .3s ease}.card:hover:before{animation:gradientShift 3s ease-in-out infinite}@keyframes gradientShift{0%,to{background-position:0% 50%}50%{background-position:100% 50%}}.card:hover{transform:translateY(-2px);box-shadow:0 12px 40px #6a7bb433,0 8px 24px #ff71ce26}.btn{padding:10px 20px;border:none;border-radius:12px;cursor:pointer;font-size:14px;font-weight:600;transition:all .3s ease-out;position:relative;overflow:hidden;box-shadow:0 2px 8px #0000001a;white-space:nowrap}.btn:before{content:"";position:absolute;top:50%;left:50%;width:0;height:0;background:#ffffff4d;border-radius:50%;transform:translate(-50%,-50%);transition:width .6s,height .6s}.btn:hover:before{width:300px;height:300px}.btn:hover{transform:translateY(-1px);box-shadow:0 4px 16px #00000026}.btn:active{transform:translateY(0)}.btn:disabled{opacity:.4;cursor:not-allowed}.btn-primary{background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));color:#fff;position:relative;overflow:hidden}.btn-primary:before{content:"";position:absolute;top:0;left:-100%;width:100%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.3),transparent);transition:left .5s ease}.btn-primary:hover:before{left:100%}.btn-primary:hover{transform:translateY(-2px);box-shadow:0 8px 25px #04785766}.btn-danger{background:var(--accent-danger);color:#fff}.btn-danger:hover{background:#ea580c}.btn-success{background:var(--accent-success);color:#fff}.btn-success:hover{background:#059669}.btn-secondary{background:var(--accent-secondary);color:#fff}.btn-secondary:hover{background:var(--accent-primary)}.btn-sm{padding:4px 8px}table{width:100%;border-collapse:collapse;margin-top:16px;border-radius:12px;overflow:hidden;box-shadow:0 4px 16px #6a7bb41a}table th,table td{padding:16px;text-align:left;border-bottom:1px solid var(--border-secondary);color:var(--text-primary)}table th{background:var(--bg-table-header);font-weight:700;color:var(--text-secondary);font-family:Fredoka,sans-serif;font-size:14px}.form-group{margin-bottom:20px}.form-group label{display:block;margin-bottom:8px;color:var(--text-primary);font-size:14px;font-weight:600;font-family:Nunito,sans-serif}.form-group input,.form-group select,.form-group textarea{width:100%;padding:12px 16px;border:2px solid var(--border-primary);border-radius:12px;font-size:14px;font-family:Nunito,sans-serif;background:var(--bg-secondary);color:var(--text-primary);transition:all .3s ease-out}.form-group input:focus,.form-group select:focus,.form-group textarea:focus{outline:none;border-color:var(--accent-primary);box-shadow:0 0 0 3px var(--shadow-secondary);background:var(--bg-secondary)}.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:#000000b3;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);display:flex;justify-content:center;align-items:center;z-index:1000;animation:modalFadeIn .3s ease-out}@keyframes modalFadeIn{0%{opacity:0}to{opacity:1}}.modal{background:var(--bg-secondary);-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);border-radius:24px;padding:32px;min-width:500px;max-width:90%;max-height:90%;overflow-y:auto;box-shadow:0 20px 60px var(--shadow-primary),0 8px 32px var(--shadow-secondary);border:1px solid var(--border-secondary);animation:modalSlideIn .4s ease-out}@keyframes modalSlideIn{0%{opacity:0;transform:translateY(-20px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}.modal-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.modal-header h2{font-size:20px;color:var(--text-primary)}.modal-footer{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}.action-buttons{display:flex;gap:8px}.badge{display:inline-block;padding:6px 12px;border-radius:20px;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;box-shadow:0 2px 8px #0000001a;margin-top:-40px;margin-right:-24px}.badge-success{background:var(--accent-success);color:#fff}.badge-warning{background:var(--accent-warning);color:#fff}.badge-danger{background:var(--accent-danger);color:#fff}.empty-state{text-align:center;padding:60px 20px;color:var(--text-muted);font-family:Nunito,sans-serif}.empty-state p{margin-bottom:20px;font-size:16px;font-weight:500}.toolbar{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.theme-toggle{position:absolute;bottom:20px;left:20px;right:20px;display:flex;align-items:center;justify-content:center;gap:12px;padding:12px;background:#ffffff14;border-radius:12px;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.1);transition:all .3s ease-out}.theme-toggle:hover{background:#ffffff1f}.theme-toggle button{background:none;border:none;color:var(--text-sidebar);cursor:pointer;padding:8px;border-radius:8px;transition:all .3s ease-out;display:flex;align-items:center;justify-content:center}.theme-toggle button:hover{background:#ffffff26}.theme-toggle button.active{background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));color:#fff;box-shadow:0 2px 8px var(--shadow-primary),0 0 8px #04785780;animation:pulse 2s ease-in-out infinite}@keyframes pulse{0%,to{transform:scale(1)}50%{transform:scale(1.05)}}@media (prefers-reduced-motion: reduce){.nav-menu a,.card,.btn,.form-group input,.form-group select,.form-group textarea,.modal-overlay,.modal,table tr{transition:none;animation:none}.nav-menu a:hover,.card:hover,.btn:hover,table tr:hover{transform:none}.btn:before{display:none}}.markdown-content{line-height:1.7;color:var(--text-primary);max-width:100%;overflow-x:auto}.markdown-content h1{font-size:2em;font-weight:700;margin-top:0;margin-bottom:.8em;color:var(--text-primary);border-bottom:2px solid var(--border-color);padding-bottom:.3em}.markdown-content h2{font-size:1.6em;font-weight:600;margin-top:1.5em;margin-bottom:.6em;color:var(--text-primary);border-bottom:1px solid var(--border-color);padding-bottom:.25em}.markdown-content h3{font-size:1.3em;font-weight:600;margin-top:1.2em;margin-bottom:.5em;color:var(--text-primary)}.markdown-content h4{font-size:1.1em;font-weight:600;margin-top:1em;margin-bottom:.4em;color:var(--text-primary)}.markdown-content p{margin-bottom:1em;line-height:1.8}.markdown-content ul,.markdown-content ol{margin-bottom:1em;padding-left:2em}.markdown-content li{margin-bottom:.5em;line-height:1.7}.markdown-content code{background:var(--bg-secondary);padding:.2em .4em;border-radius:4px;font-size:.9em;font-family:Monaco,Menlo,Ubuntu Mono,Consolas,monospace;color:var(--accent-secondary);border:1px solid var(--border-color)}.markdown-content pre{background:var(--bg-secondary);padding:1em;border-radius:8px;overflow-x:auto;margin-bottom:1em;border:1px solid var(--border-color)}.markdown-content pre code{background:none;padding:0;border:none;color:var(--text-primary);font-size:.95em}.markdown-content blockquote{border-left:4px solid var(--accent-primary);padding-left:1em;margin-left:0;margin-bottom:1em;color:var(--text-secondary);font-style:italic}.markdown-content a{color:var(--accent-primary);text-decoration:none;border-bottom:1px solid transparent;transition:all .2s ease}.markdown-content a:hover{color:var(--accent-secondary);border-bottom-color:var(--accent-secondary)}.markdown-content table{width:100%;border-collapse:collapse;margin-bottom:1em}.markdown-content table th,.markdown-content table td{padding:.75em;text-align:left;border:1px solid var(--border-color)}.markdown-content table th{background:var(--bg-secondary);font-weight:600}.markdown-content hr{border:none;border-top:2px solid var(--border-color);margin:2em 0}.markdown-content strong{font-weight:600;color:var(--text-primary)}.markdown-content em{font-style:italic}.markdown-content img{max-width:100%;height:auto;border-radius:8px;margin:1em 0}*{margin:0;padding:0;box-sizing:border-box}:root{--bg-primary: linear-gradient(135deg, #F7FEE7 0%, #F0FDF4 100%);--bg-secondary: rgba(255, 255, 255, .95);--bg-sidebar: linear-gradient(135deg, #0F5132 0%, #065F46 100%);--bg-card: rgba(255, 255, 255, .98);--bg-code: #f4fff7;--bg-table-header: linear-gradient(135deg, #A7F3D0 0%, #6EE7B7 100%);--text-primary: #14532D;--text-secondary: #064E3B;--text-muted: #065F46;--text-sidebar: #FFFFFF;--text-on-dark: #FFFFFF;--border-primary: rgba(15, 81, 50, .2);--border-secondary: rgba(6, 95, 70, .2);--accent-primary: #0F5132;--accent-secondary: #047857;--accent-light: #a1e9c7;--accent-success: #047857;--accent-warning: #D97706;--accent-danger: #DC2626;--shadow-primary: rgba(15, 81, 50, .3);--shadow-secondary: rgba(15, 81, 50, .15);--bg-route-item: rgba(248, 249, 250, .9);--bg-route-item-hover: rgba(230, 244, 234, .95);--bg-route-item-selected: rgba(161, 233, 199, .25);--text-route-muted: #6c757d;--text-info: #2faeee}[data-theme=dark]{--bg-primary: linear-gradient(135deg, #0A1A0F 0%, #0F2415 100%);--bg-secondary: rgba(15, 36, 21, .9);--bg-sidebar: linear-gradient(135deg, #0F5132 0%, #065F46 100%);--bg-card: rgba(25, 51, 31, .95);--bg-code: #0f172a;--bg-table-header: linear-gradient(135deg, #0F5132 0%, #047857 100%);--text-primary: #ECFEF5;--text-secondary: #A7F3D0;--text-muted: #6EE7B7;--text-sidebar: #FFFFFF;--text-on-dark: #ECFEF5;--border-primary: rgba(15, 81, 50, .5);--border-secondary: rgba(6, 95, 70, .5);--accent-primary: #0F5132;--accent-secondary: #047857;--accent-light: #002d18;--accent-success: #10B981;--accent-warning: #F59E0B;--accent-danger: #EF4444;--shadow-primary: rgba(15, 81, 50, .4);--shadow-secondary: rgba(6, 95, 70, .3);--bg-route-item: rgba(20, 46, 28, .7);--bg-route-item-hover: rgba(30, 60, 38, .85);--bg-route-item-selected: rgba(16, 185, 129, .15);--text-route-muted: #A7F3D0}body{font-family:Nunito,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background:var(--bg-primary);color:var(--text-primary);transition:all .3s ease;min-height:100vh}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}#root{width:100vw;height:100vh;overflow:hidden}
1
+ .app{display:flex;width:100%;height:100%}.sidebar{width:260px;background:var(--bg-sidebar);color:var(--text-primary);display:flex;flex-direction:column;padding:0 0 80px;border-radius:20px;box-shadow:0 8px 32px var(--shadow-primary),0 0 20px #0478574d;margin:16px;position:relative;z-index:1;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.1)}.logo{padding:28px 20px;border-bottom:2px solid rgba(255,255,255,.2);text-align:center;position:relative;overflow:hidden}.logo:before{content:"✨";position:absolute;top:12px;left:12px;font-size:16px;opacity:.7;transition:all .3s ease}.logo:after{content:"🌟";position:absolute;top:12px;right:12px;font-size:16px;opacity:.7;transition:all .3s ease}.logo:hover:before,.logo:focus:before{animation:sparkle 3s ease-in-out infinite}.logo:hover:after,.logo:focus:after{animation:sparkle 3s ease-in-out infinite 1.5s}@keyframes sparkle{0%,to{transform:scale(1) rotate(0);opacity:.7}50%{transform:scale(1.2) rotate(180deg);opacity:1}}.logo h2{font-size:22px;font-weight:800;background:linear-gradient(135deg,#fff,#f8f8ff,#e6e6fa);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;text-shadow:0 2px 4px rgba(0,0,0,.1),0 0 20px rgba(4,120,87,.3);position:relative;z-index:1;letter-spacing:1px;text-transform:uppercase;transition:all .3s ease}.logo:hover h2,.logo:focus h2{animation:logoGlow 4s ease-in-out infinite}@keyframes logoGlow{0%,to{text-shadow:0 2px 4px rgba(0,0,0,.1),0 0 20px rgba(4,120,87,.3)}50%{text-shadow:0 2px 4px rgba(0,0,0,.1),0 0 30px rgba(4,120,87,.5),0 0 40px rgba(4,120,87,.3)}}.nav-menu{list-style:none;padding:16px 0;margin:0}.nav-menu li{margin:8px 12px}.nav-menu a{display:block;padding:14px 20px;color:var(--text-sidebar);text-decoration:none;border-radius:12px;transition:all .3s ease-out;font-weight:500;position:relative;overflow:hidden}.nav-menu a:before{content:"";position:absolute;top:0;left:-100%;width:100%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.15),transparent);transition:left .5s ease-out}.nav-menu a:hover:before{left:100%}.nav-menu a:hover{background-color:#ffffff1a;transform:translate(4px);box-shadow:0 4px 12px #0f172a33}.nav-menu a.active{background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));box-shadow:0 4px 16px var(--shadow-primary)}.main-content{flex:1;padding:24px;overflow-y:auto;background:var(--bg-primary);position:relative;z-index:1;margin:16px 16px 16px 0;border-radius:20px}.main-content:before{content:"";position:absolute;top:0;left:0;right:0;bottom:0;background-image:radial-gradient(circle at 20% 80%,rgba(255,206,92,.1) 0%,transparent 50%),radial-gradient(circle at 80% 20%,rgba(134,204,202,.1) 0%,transparent 50%),radial-gradient(circle at 40% 40%,rgba(255,113,206,.05) 0%,transparent 50%);pointer-events:none}.page-header{margin-bottom:24px;position:relative;z-index:1}.page-header h1{font-family:Fredoka,sans-serif;font-size:28px;font-weight:700;color:var(--accent-primary);margin-bottom:8px;text-shadow:0 2px 4px var(--shadow-primary)}.page-header p{color:var(--text-muted);font-size:16px;font-weight:400}.card{background:var(--bg-card);border-radius:20px;padding:24px;box-shadow:0 8px 32px var(--shadow-secondary),0 0 16px #0478571a;margin-bottom:24px;border:1px solid var(--border-primary);position:relative;overflow:hidden;transition:all .3s ease-out;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.card:before{content:"";position:absolute;top:0;left:0;right:0;height:4px;background:linear-gradient(90deg,var(--accent-primary),var(--accent-secondary),var(--accent-primary));border-radius:20px 20px 0 0;background-size:200% 100%;transition:all .3s ease}.card:hover:before{animation:gradientShift 3s ease-in-out infinite}@keyframes gradientShift{0%,to{background-position:0% 50%}50%{background-position:100% 50%}}.card:hover{transform:translateY(-2px);box-shadow:0 12px 40px #6a7bb433,0 8px 24px #ff71ce26}.btn{padding:10px 20px;border:none;border-radius:12px;cursor:pointer;font-size:14px;font-weight:600;transition:all .3s ease-out;position:relative;overflow:hidden;box-shadow:0 2px 8px #0000001a;white-space:nowrap}.btn:before{content:"";position:absolute;top:50%;left:50%;width:0;height:0;background:#ffffff4d;border-radius:50%;transform:translate(-50%,-50%);transition:width .6s,height .6s}.btn:hover:before{width:300px;height:300px}.btn:hover{transform:translateY(-1px);box-shadow:0 4px 16px #00000026}.btn:active{transform:translateY(0)}.btn:disabled{opacity:.4;cursor:not-allowed}.btn-primary{background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));color:#fff;position:relative;overflow:hidden}.btn-primary:before{content:"";position:absolute;top:0;left:-100%;width:100%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.3),transparent);transition:left .5s ease}.btn-primary:hover:before{left:100%}.btn-primary:hover{transform:translateY(-2px);box-shadow:0 8px 25px #04785766}.btn-danger{background:var(--accent-danger);color:#fff}.btn-danger:hover{background:#ea580c}.btn-success{background:var(--accent-success);color:#fff}.btn-success:hover{background:#059669}.btn-secondary{background:var(--accent-secondary);color:#fff}.btn-secondary:hover{background:var(--accent-primary)}.btn-sm{padding:4px 8px}table{width:100%;border-collapse:collapse;margin-top:16px;border-radius:12px;overflow:hidden;box-shadow:0 4px 16px #6a7bb41a}table th,table td{padding:16px;text-align:left;border-bottom:1px solid var(--border-secondary);color:var(--text-primary)}table th{background:var(--bg-table-header);font-weight:700;color:var(--text-secondary);font-family:Fredoka,sans-serif;font-size:14px}.form-group{margin-bottom:20px}.form-group label{display:block;margin-bottom:8px;color:var(--text-primary);font-size:14px;font-weight:600;font-family:Nunito,sans-serif}.form-group input,.form-group select,.form-group textarea{width:100%;padding:12px 16px;border:2px solid var(--border-primary);border-radius:12px;font-size:14px;font-family:Nunito,sans-serif;background:var(--bg-secondary);color:var(--text-primary);transition:all .3s ease-out}.form-group input:focus,.form-group select:focus,.form-group textarea:focus{outline:none;border-color:var(--accent-primary);box-shadow:0 0 0 3px var(--shadow-secondary);background:var(--bg-secondary)}.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:#000000b3;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);display:flex;justify-content:center;align-items:center;z-index:1000;animation:modalFadeIn .3s ease-out}@keyframes modalFadeIn{0%{opacity:0}to{opacity:1}}.modal{background:var(--bg-secondary);-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);border-radius:24px;padding:32px;min-width:500px;max-width:90%;max-height:90%;overflow-y:auto;box-shadow:0 20px 60px var(--shadow-primary),0 8px 32px var(--shadow-secondary);border:1px solid var(--border-secondary);animation:modalSlideIn .4s ease-out}@keyframes modalSlideIn{0%{opacity:0;transform:translateY(-20px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}.modal-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.modal-header h2{font-size:20px;color:var(--text-primary)}.modal-footer{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}.action-buttons{display:flex;gap:8px}.badge{display:inline-block;padding:6px 12px;border-radius:20px;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;box-shadow:0 2px 8px #0000001a;margin-top:-40px;margin-right:-24px}.badge-success{background:var(--accent-success);color:#fff}.badge-warning{background:var(--accent-warning);color:#fff}.badge-danger{background:var(--accent-danger);color:#fff}.empty-state{text-align:center;padding:60px 20px;color:var(--text-muted);font-family:Nunito,sans-serif}.empty-state p{margin-bottom:20px;font-size:16px;font-weight:500}.toolbar{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.theme-toggle{position:absolute;bottom:20px;left:20px;right:20px;display:flex;align-items:center;justify-content:center;gap:12px;padding:12px;background:#ffffff14;border-radius:12px;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.1);transition:all .3s ease-out}.theme-toggle:hover{background:#ffffff1f}.theme-toggle button{background:none;border:none;color:var(--text-sidebar);cursor:pointer;padding:8px;border-radius:8px;transition:all .3s ease-out;display:flex;align-items:center;justify-content:center}.theme-toggle button:hover{background:#ffffff26}.theme-toggle button.active{background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));color:#fff;box-shadow:0 2px 8px var(--shadow-primary),0 0 8px #04785780;animation:pulse 2s ease-in-out infinite}@keyframes pulse{0%,to{transform:scale(1)}50%{transform:scale(1.05)}}@media (prefers-reduced-motion: reduce){.nav-menu a,.card,.btn,.form-group input,.form-group select,.form-group textarea,.modal-overlay,.modal,table tr{transition:none;animation:none}.nav-menu a:hover,.card:hover,.btn:hover,table tr:hover{transform:none}.btn:before{display:none}}.markdown-content{line-height:1.7;color:var(--text-primary);max-width:100%;overflow-x:auto}.markdown-content h1{font-size:2em;font-weight:700;margin-top:0;margin-bottom:.8em;color:var(--text-primary);border-bottom:2px solid var(--border-color);padding-bottom:.3em}.markdown-content h2{font-size:1.6em;font-weight:600;margin-top:1.5em;margin-bottom:.6em;color:var(--text-primary);border-bottom:1px solid var(--border-color);padding-bottom:.25em}.markdown-content h3{font-size:1.3em;font-weight:600;margin-top:1.2em;margin-bottom:.5em;color:var(--text-primary)}.markdown-content h4{font-size:1.1em;font-weight:600;margin-top:1em;margin-bottom:.4em;color:var(--text-primary)}.markdown-content p{margin-bottom:1em;line-height:1.8}.markdown-content ul,.markdown-content ol{margin-bottom:1em;padding-left:2em}.markdown-content li{margin-bottom:.5em;line-height:1.7}.markdown-content code{background:var(--bg-secondary);padding:.2em .4em;border-radius:4px;font-size:.9em;font-family:Monaco,Menlo,Ubuntu Mono,Consolas,monospace;color:var(--accent-secondary);border:1px solid var(--border-color)}.markdown-content pre{background:var(--bg-secondary);padding:1em;border-radius:8px;overflow-x:auto;margin-bottom:1em;border:1px solid var(--border-color)}.markdown-content pre code{background:none;padding:0;border:none;color:var(--text-primary);font-size:.95em}.markdown-content blockquote{border-left:4px solid var(--accent-primary);padding-left:1em;margin-left:0;margin-bottom:1em;color:var(--text-secondary);font-style:italic}.markdown-content a{color:var(--accent-primary);text-decoration:none;border-bottom:1px solid transparent;transition:all .2s ease}.markdown-content a:hover{color:var(--accent-secondary);border-bottom-color:var(--accent-secondary)}.markdown-content table{width:100%;border-collapse:collapse;margin-bottom:1em}.markdown-content table th,.markdown-content table td{padding:.75em;text-align:left;border:1px solid var(--border-color)}.markdown-content table th{background:var(--bg-secondary);font-weight:600}.markdown-content hr{border:none;border-top:2px solid var(--border-color);margin:2em 0}.markdown-content strong{font-weight:600;color:var(--text-primary)}.markdown-content em{font-style:italic}.markdown-content img{max-width:100%;height:auto;border-radius:8px;margin:1em 0}*{margin:0;padding:0;box-sizing:border-box}:root{--bg-primary: linear-gradient(135deg, #F7FEE7 0%, #F0FDF4 100%);--bg-secondary: rgba(255, 255, 255, .95);--bg-sidebar: linear-gradient(135deg, #0F5132 0%, #065F46 100%);--bg-card: rgba(255, 255, 255, .98);--bg-code: #f4fff7;--bg-table-header: linear-gradient(135deg, #A7F3D0 0%, #6EE7B7 100%);--text-primary: #14532D;--text-secondary: #064E3B;--text-muted: #065F46;--text-sidebar: #FFFFFF;--text-on-dark: #FFFFFF;--border-primary: rgba(15, 81, 50, .2);--border-secondary: rgba(6, 95, 70, .2);--accent-primary: #0F5132;--accent-secondary: #047857;--accent-light: #a1e9c7;--accent-success: #047857;--accent-warning: #D97706;--accent-danger: #DC2626;--shadow-primary: rgba(15, 81, 50, .3);--shadow-secondary: rgba(15, 81, 50, .15);--bg-route-item: rgba(248, 249, 250, .9);--bg-route-item-hover: rgba(230, 244, 234, .95);--bg-route-item-selected: rgba(161, 233, 199, .25);--text-route-muted: #6c757d;--text-info: #2faeee}[data-theme=dark]{--bg-primary: linear-gradient(135deg, #0A1A0F 0%, #0F2415 100%);--bg-secondary: rgba(15, 36, 21, .9);--bg-sidebar: linear-gradient(135deg, #0F5132 0%, #065F46 100%);--bg-card: rgba(25, 51, 31, .95);--bg-code: #0f172a;--bg-table-header: linear-gradient(135deg, #0F5132 0%, #047857 100%);--text-primary: #ECFEF5;--text-secondary: #A7F3D0;--text-muted: #6EE7B7;--text-sidebar: #FFFFFF;--text-on-dark: #ECFEF5;--border-primary: rgba(15, 81, 50, .5);--border-secondary: rgba(6, 95, 70, .5);--accent-primary: #0F5132;--accent-secondary: #047857;--accent-light: #002d18;--accent-success: #10B981;--accent-warning: #F59E0B;--accent-danger: #EF4444;--shadow-primary: rgba(15, 81, 50, .4);--shadow-secondary: rgba(6, 95, 70, .3);--bg-route-item: rgba(20, 46, 28, .7);--bg-route-item-hover: rgba(30, 60, 38, .85);--bg-route-item-selected: rgba(16, 185, 129, .15);--text-route-muted: #A7F3D0}body{font-family:Nunito,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background:var(--bg-primary);color:var(--text-primary);transition:all .3s ease;min-height:100vh}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}#root{width:100vw;height:100vh;overflow:hidden}::-webkit-scrollbar{width:12px;height:12px}::-webkit-scrollbar-track{background:var(--bg-secondary);border-radius:8px}::-webkit-scrollbar-thumb{background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));border-radius:8px;border:2px solid var(--bg-secondary);box-shadow:0 2px 4px #0000001a;transition:all .3s ease}::-webkit-scrollbar-thumb:hover{background:linear-gradient(135deg,var(--accent-secondary),var(--accent-primary));box-shadow:0 4px 8px #0003;transform:scale(1.1)}::-webkit-scrollbar-thumb:active{background:var(--accent-primary);box-shadow:0 6px 12px #0000004d}::-webkit-scrollbar-corner{background:var(--bg-secondary)}*{scrollbar-width:thin;scrollbar-color:var(--accent-primary) var(--bg-secondary)}::-webkit-scrollbar-button{display:none}html{scroll-behavior:smooth}