Browse Source

feat: add TG Admin template with multiple pages and assets

- Introduce a new admin template for TG Live Game, including HTML pages for dashboard, user management, settings, and authentication.
- Implement Tailwind CSS for styling and responsive design.
- Add JavaScript functionality for sidebar and dropdown menus.
- Include various assets such as images and stylesheets to enhance the user interface.
yb 2 weeks ago
parent
commit
5f9425e002
30 changed files with 4393 additions and 589 deletions
  1. BIN
      .playwright-mcp/page-2026-01-09T16-37-28-232Z.png
  2. BIN
      .playwright-mcp/page-2026-01-09T16-37-49-611Z.png
  3. BIN
      .playwright-mcp/page-2026-01-09T16-38-04-198Z.png
  4. BIN
      .playwright-mcp/page-2026-01-09T16-40-46-326Z.png
  5. BIN
      .playwright-mcp/page-2026-01-09T16-41-07-501Z.png
  6. BIN
      .playwright-mcp/page-2026-01-09T16-41-20-546Z.png
  7. BIN
      .playwright-mcp/page-2026-01-09T16-41-37-915Z.png
  8. BIN
      .playwright-mcp/page-2026-01-09T16-56-48-608Z.png
  9. BIN
      .playwright-mcp/page-2026-01-09T16-57-23-895Z.png
  10. 13 0
      Prototype/admin-template/index.html
  11. 17 0
      Prototype/admin-template/package.json
  12. 40 0
      Prototype/admin-template/pages/404.html
  13. 140 0
      Prototype/admin-template/pages/auth/login.html
  14. 153 0
      Prototype/admin-template/pages/auth/register.html
  15. 246 0
      Prototype/admin-template/pages/dashboard.html
  16. 197 0
      Prototype/admin-template/pages/settings.html
  17. 301 0
      Prototype/admin-template/pages/users.html
  18. 1195 0
      Prototype/admin-template/pnpm-lock.yaml
  19. 6 0
      Prototype/admin-template/postcss.config.js
  20. 70 0
      Prototype/admin-template/src/js/main.js
  21. 110 0
      Prototype/admin-template/src/styles/main.css
  22. 21 0
      Prototype/admin-template/tailwind.config.js
  23. 19 0
      Prototype/admin-template/vite.config.js
  24. 175 0
      Prototype/login/login-responsive.html
  25. 575 221
      src/layout/index.vue
  26. 37 2
      src/locales/en.json
  27. 37 2
      src/locales/zh-cn.json
  28. 10 0
      src/store/app.ts
  29. 409 207
      src/views/dashboard/index.vue
  30. 622 157
      src/views/login/index.vue

BIN
.playwright-mcp/page-2026-01-09T16-37-28-232Z.png


BIN
.playwright-mcp/page-2026-01-09T16-37-49-611Z.png


BIN
.playwright-mcp/page-2026-01-09T16-38-04-198Z.png


BIN
.playwright-mcp/page-2026-01-09T16-40-46-326Z.png


BIN
.playwright-mcp/page-2026-01-09T16-41-07-501Z.png


BIN
.playwright-mcp/page-2026-01-09T16-41-20-546Z.png


BIN
.playwright-mcp/page-2026-01-09T16-41-37-915Z.png


BIN
.playwright-mcp/page-2026-01-09T16-56-48-608Z.png


BIN
.playwright-mcp/page-2026-01-09T16-57-23-895Z.png


+ 13 - 0
Prototype/admin-template/index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="refresh" content="0;url=pages/dashboard.html">
+  <title>TG Admin - Redirecting...</title>
+</head>
+<body>
+  <p>Redirecting to dashboard...</p>
+  <script>window.location.href = 'pages/dashboard.html';</script>
+</body>
+</html>

+ 17 - 0
Prototype/admin-template/package.json

@@ -0,0 +1,17 @@
+{
+  "name": "tg-admin-template",
+  "version": "1.0.0",
+  "description": "SaaS Admin Dashboard Template - TG Live Game Style",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview"
+  },
+  "devDependencies": {
+    "vite": "^5.4.0",
+    "tailwindcss": "^3.4.0",
+    "postcss": "^8.4.0",
+    "autoprefixer": "^10.4.0"
+  }
+}

+ 40 - 0
Prototype/admin-template/pages/404.html

@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>404 - 页面未找到</title>
+  <link rel="stylesheet" href="../src/styles/main.css">
+</head>
+<body class="min-h-screen bg-white flex items-center justify-center p-4">
+  <div class="text-center max-w-md">
+    <!-- 404 Number -->
+    <div class="text-8xl sm:text-9xl font-extrabold text-black tracking-tight mb-4">
+      404
+    </div>
+
+    <!-- Message -->
+    <h1 class="text-2xl sm:text-3xl font-bold text-black mb-4">
+      页面未找到
+    </h1>
+    <p class="text-gray-400 mb-8">
+      抱歉,您访问的页面不存在或已被移除。
+    </p>
+
+    <!-- Actions -->
+    <div class="flex flex-col sm:flex-row items-center justify-center gap-4">
+      <a href="dashboard.html" class="btn-primary w-full sm:w-auto px-8">
+        返回首页
+      </a>
+      <button onclick="history.back()" class="btn-secondary w-full sm:w-auto px-8">
+        返回上一页
+      </button>
+    </div>
+
+    <!-- Footer -->
+    <p class="text-gray-300 text-sm mt-12">
+      &copy; 2025 TG Live Game
+    </p>
+  </div>
+</body>
+</html>

+ 140 - 0
Prototype/admin-template/pages/auth/login.html

@@ -0,0 +1,140 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>登录 - TG Admin</title>
+  <link rel="stylesheet" href="../../src/styles/main.css">
+</head>
+<body class="min-h-screen bg-white">
+  <div class="min-h-screen flex flex-col lg:flex-row">
+
+    <!-- Left Panel - Hidden on mobile -->
+    <div class="hidden lg:flex lg:w-1/2 xl:w-2/5 bg-gradient-to-br from-gray-900 via-gray-800 to-black p-8 xl:p-12 flex-col justify-between">
+      <!-- Logo -->
+      <div class="flex items-center gap-3">
+        <div class="w-10 h-10 bg-white rounded-lg flex items-center justify-center">
+          <svg class="w-5 h-5 text-black" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
+          </svg>
+        </div>
+        <span class="text-white font-semibold text-lg">TG Admin</span>
+      </div>
+
+      <!-- Center content -->
+      <div class="flex-1 flex flex-col justify-center">
+        <h2 class="text-3xl xl:text-4xl font-bold text-white mb-4 leading-tight">
+          欢迎回来
+        </h2>
+        <p class="text-gray-400 text-base xl:text-lg max-w-sm">
+          登录您的管理后台,开始管理您的业务
+        </p>
+
+        <!-- Stats -->
+        <div class="flex gap-8 mt-10">
+          <div>
+            <div class="text-2xl xl:text-3xl font-bold text-white">50K+</div>
+            <div class="text-gray-500 text-sm">用户</div>
+          </div>
+          <div>
+            <div class="text-2xl xl:text-3xl font-bold text-white">99.9%</div>
+            <div class="text-gray-500 text-sm">稳定性</div>
+          </div>
+          <div>
+            <div class="text-2xl xl:text-3xl font-bold text-white">24/7</div>
+            <div class="text-gray-500 text-sm">技术支持</div>
+          </div>
+        </div>
+      </div>
+
+      <!-- Footer -->
+      <div class="text-gray-600 text-sm">
+        &copy; 2025 TG Live Game. All rights reserved.
+      </div>
+    </div>
+
+    <!-- Right Panel - Login Form -->
+    <div class="flex-1 flex items-center justify-center p-6 sm:p-8 md:p-12 lg:p-16">
+      <div class="w-full max-w-sm sm:max-w-md">
+
+        <!-- Mobile Logo -->
+        <div class="flex items-center gap-2 mb-8 sm:mb-12 lg:hidden">
+          <div class="w-8 h-8 sm:w-10 sm:h-10 bg-black rounded-lg flex items-center justify-center">
+            <svg class="w-4 h-4 sm:w-5 sm:h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
+            </svg>
+          </div>
+          <span class="font-semibold text-black text-base sm:text-lg">TG Admin</span>
+        </div>
+
+        <!-- Title -->
+        <div class="mb-8 sm:mb-10 lg:mb-12">
+          <h1 class="text-3xl sm:text-4xl lg:text-5xl font-extrabold text-black tracking-tight leading-none mb-2 sm:mb-3">
+            登录
+          </h1>
+          <p class="text-gray-400 text-base sm:text-lg">欢迎回来</p>
+        </div>
+
+        <!-- Form -->
+        <form class="space-y-6 sm:space-y-8" action="../dashboard.html">
+          <div>
+            <input type="text" placeholder="用户名" class="input-underline text-lg">
+          </div>
+
+          <div>
+            <input type="password" placeholder="密码" class="input-underline text-lg">
+          </div>
+
+          <div class="flex items-center justify-between text-sm sm:text-base">
+            <label class="flex items-center text-gray-500 cursor-pointer group">
+              <input type="checkbox" class="w-4 h-4 sm:w-5 sm:h-5 rounded border-gray-300 text-black focus:ring-black mr-2 sm:mr-3 cursor-pointer">
+              <span class="group-hover:text-gray-700">记住我</span>
+            </label>
+            <a href="#" class="text-black font-medium hover:underline underline-offset-4">忘记密码?</a>
+          </div>
+
+          <button type="submit" class="btn-primary text-lg py-4">
+            登录
+          </button>
+        </form>
+
+        <!-- Divider -->
+        <div class="flex items-center my-8 sm:my-10">
+          <div class="flex-1 h-px bg-gray-200"></div>
+          <span class="px-4 sm:px-6 text-gray-300 text-xs sm:text-sm uppercase tracking-wider">或</span>
+          <div class="flex-1 h-px bg-gray-200"></div>
+        </div>
+
+        <!-- Social Login -->
+        <div class="space-y-3">
+          <button class="btn-secondary flex items-center justify-center gap-3">
+            <svg class="w-5 h-5" viewBox="0 0 24 24">
+              <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
+              <path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
+              <path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
+              <path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
+            </svg>
+            使用 Google 继续
+          </button>
+          <button class="btn-secondary flex items-center justify-center gap-3">
+            <svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
+              <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
+            </svg>
+            使用 GitHub 继续
+          </button>
+        </div>
+
+        <!-- Footer -->
+        <p class="text-center text-gray-400 text-sm sm:text-base mt-8 sm:mt-12">
+          还没有账户?<a href="register.html" class="text-black font-medium hover:underline underline-offset-4">注册</a>
+        </p>
+
+        <!-- Mobile Footer -->
+        <p class="text-center text-gray-400 text-xs mt-8 lg:hidden">
+          &copy; 2025 TG Live Game
+        </p>
+      </div>
+    </div>
+  </div>
+</body>
+</html>

+ 153 - 0
Prototype/admin-template/pages/auth/register.html

@@ -0,0 +1,153 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>注册 - TG Admin</title>
+  <link rel="stylesheet" href="../../src/styles/main.css">
+</head>
+<body class="min-h-screen bg-white">
+  <div class="min-h-screen flex flex-col lg:flex-row">
+
+    <!-- Left Panel - Hidden on mobile -->
+    <div class="hidden lg:flex lg:w-1/2 xl:w-2/5 bg-gradient-to-br from-gray-900 via-gray-800 to-black p-8 xl:p-12 flex-col justify-between">
+      <!-- Logo -->
+      <div class="flex items-center gap-3">
+        <div class="w-10 h-10 bg-white rounded-lg flex items-center justify-center">
+          <svg class="w-5 h-5 text-black" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
+          </svg>
+        </div>
+        <span class="text-white font-semibold text-lg">TG Admin</span>
+      </div>
+
+      <!-- Center content -->
+      <div class="flex-1 flex flex-col justify-center">
+        <h2 class="text-3xl xl:text-4xl font-bold text-white mb-4 leading-tight">
+          加入我们
+        </h2>
+        <p class="text-gray-400 text-base xl:text-lg max-w-sm">
+          创建账户,开始您的管理之旅
+        </p>
+
+        <!-- Features -->
+        <div class="mt-10 space-y-4">
+          <div class="flex items-center gap-3 text-gray-300">
+            <svg class="w-5 h-5 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
+            </svg>
+            <span>免费开始使用</span>
+          </div>
+          <div class="flex items-center gap-3 text-gray-300">
+            <svg class="w-5 h-5 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
+            </svg>
+            <span>无需信用卡</span>
+          </div>
+          <div class="flex items-center gap-3 text-gray-300">
+            <svg class="w-5 h-5 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
+            </svg>
+            <span>随时取消</span>
+          </div>
+        </div>
+      </div>
+
+      <!-- Footer -->
+      <div class="text-gray-600 text-sm">
+        &copy; 2025 TG Live Game. All rights reserved.
+      </div>
+    </div>
+
+    <!-- Right Panel - Register Form -->
+    <div class="flex-1 flex items-center justify-center p-6 sm:p-8 md:p-12 lg:p-16">
+      <div class="w-full max-w-sm sm:max-w-md">
+
+        <!-- Mobile Logo -->
+        <div class="flex items-center gap-2 mb-8 sm:mb-12 lg:hidden">
+          <div class="w-8 h-8 sm:w-10 sm:h-10 bg-black rounded-lg flex items-center justify-center">
+            <svg class="w-4 h-4 sm:w-5 sm:h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
+            </svg>
+          </div>
+          <span class="font-semibold text-black text-base sm:text-lg">TG Admin</span>
+        </div>
+
+        <!-- Title -->
+        <div class="mb-8 sm:mb-10 lg:mb-12">
+          <h1 class="text-3xl sm:text-4xl lg:text-5xl font-extrabold text-black tracking-tight leading-none mb-2 sm:mb-3">
+            注册
+          </h1>
+          <p class="text-gray-400 text-base sm:text-lg">创建您的账户</p>
+        </div>
+
+        <!-- Form -->
+        <form class="space-y-6" action="login.html">
+          <div>
+            <input type="text" placeholder="用户名" class="input-underline text-lg">
+          </div>
+
+          <div>
+            <input type="email" placeholder="邮箱" class="input-underline text-lg">
+          </div>
+
+          <div>
+            <input type="password" placeholder="密码" class="input-underline text-lg">
+          </div>
+
+          <div>
+            <input type="password" placeholder="确认密码" class="input-underline text-lg">
+          </div>
+
+          <div class="flex items-start gap-3">
+            <input type="checkbox" class="w-5 h-5 mt-0.5 rounded border-gray-300 text-black focus:ring-black cursor-pointer">
+            <span class="text-sm text-gray-500">
+              我已阅读并同意 <a href="#" class="text-black font-medium hover:underline">服务条款</a> 和 <a href="#" class="text-black font-medium hover:underline">隐私政策</a>
+            </span>
+          </div>
+
+          <button type="submit" class="btn-primary text-lg py-4">
+            创建账户
+          </button>
+        </form>
+
+        <!-- Divider -->
+        <div class="flex items-center my-8">
+          <div class="flex-1 h-px bg-gray-200"></div>
+          <span class="px-4 text-gray-300 text-xs sm:text-sm uppercase tracking-wider">或</span>
+          <div class="flex-1 h-px bg-gray-200"></div>
+        </div>
+
+        <!-- Social Login -->
+        <div class="space-y-3">
+          <button class="btn-secondary flex items-center justify-center gap-3">
+            <svg class="w-5 h-5" viewBox="0 0 24 24">
+              <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
+              <path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
+              <path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
+              <path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
+            </svg>
+            使用 Google 注册
+          </button>
+          <button class="btn-secondary flex items-center justify-center gap-3">
+            <svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
+              <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
+            </svg>
+            使用 GitHub 注册
+          </button>
+        </div>
+
+        <!-- Footer -->
+        <p class="text-center text-gray-400 text-sm sm:text-base mt-8 sm:mt-12">
+          已有账户?<a href="login.html" class="text-black font-medium hover:underline underline-offset-4">登录</a>
+        </p>
+
+        <!-- Mobile Footer -->
+        <p class="text-center text-gray-400 text-xs mt-8 lg:hidden">
+          &copy; 2025 TG Live Game
+        </p>
+      </div>
+    </div>
+  </div>
+</body>
+</html>

+ 246 - 0
Prototype/admin-template/pages/dashboard.html

@@ -0,0 +1,246 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>Dashboard - TG Admin</title>
+  <script src="https://cdn.tailwindcss.com"></script>
+  <script>
+    tailwind.config = {
+      theme: {
+        extend: {
+          fontFamily: { sans: ['Inter', 'system-ui', 'sans-serif'] },
+          colors: { brand: { DEFAULT: '#000000', hover: '#1a1a1a' } }
+        }
+      }
+    }
+  </script>
+  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
+  <style>
+    body { font-family: 'Inter', system-ui, sans-serif; }
+    .nav-item { @apply flex items-center gap-3 px-4 py-3 text-gray-400 hover:text-white hover:bg-white/5 transition-colors cursor-pointer; }
+    .nav-item.active { @apply text-white bg-white/10; }
+    .card { @apply bg-white border border-gray-200 p-6; }
+    .badge { @apply inline-flex items-center px-2.5 py-0.5 text-xs font-medium; }
+    .badge-success { @apply bg-green-100 text-green-800; }
+  </style>
+</head>
+<body class="min-h-screen bg-gray-50">
+  <div class="flex min-h-screen">
+
+    <!-- Sidebar Overlay (Mobile) -->
+    <div id="sidebar-overlay" class="fixed inset-0 bg-black/50 z-40 lg:hidden hidden"></div>
+
+    <!-- Sidebar -->
+    <aside id="sidebar" class="fixed lg:static inset-y-0 left-0 z-50 w-64 bg-gradient-to-b from-gray-900 via-gray-900 to-black transform -translate-x-full lg:translate-x-0 transition-transform duration-200">
+      <div class="flex flex-col h-full">
+        <!-- Logo -->
+        <div class="flex items-center justify-between h-16 px-6 border-b border-white/10">
+          <div class="flex items-center gap-3">
+            <div class="w-8 h-8 bg-white rounded-lg flex items-center justify-center">
+              <svg class="w-4 h-4 text-black" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
+              </svg>
+            </div>
+            <span class="text-white font-semibold">TG Admin</span>
+          </div>
+          <!-- Close button (Mobile) -->
+          <button id="sidebar-close" class="lg:hidden text-gray-400 hover:text-white">
+            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
+            </svg>
+          </button>
+        </div>
+
+        <!-- Navigation -->
+        <nav class="flex-1 px-4 py-6 space-y-1 overflow-y-auto">
+          <a href="dashboard.html" class="nav-item active">
+            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
+            </svg>
+            <span>Dashboard</span>
+          </a>
+          <a href="users.html" class="nav-item">
+            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z"/>
+            </svg>
+            <span>用户管理</span>
+          </a>
+          <a href="settings.html" class="nav-item">
+            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
+            </svg>
+            <span>设置</span>
+          </a>
+        </nav>
+
+        <!-- Footer -->
+        <div class="px-6 py-4 border-t border-white/10">
+          <a href="auth/login.html" class="flex items-center gap-3 text-gray-400 hover:text-white transition-colors">
+            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>
+            </svg>
+            <span>退出登录</span>
+          </a>
+        </div>
+      </div>
+    </aside>
+
+    <!-- Main Content -->
+    <div class="flex-1 flex flex-col min-w-0">
+      <!-- Header -->
+      <header class="h-16 bg-white border-b border-gray-200 flex items-center justify-between px-4 lg:px-8">
+        <!-- Mobile menu button -->
+        <button id="sidebar-toggle" class="lg:hidden p-2 -ml-2 text-gray-600 hover:text-black">
+          <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
+          </svg>
+        </button>
+
+        <!-- Page Title -->
+        <h1 class="text-lg font-semibold text-black hidden lg:block">Dashboard</h1>
+
+        <!-- User Menu -->
+        <div class="relative">
+          <button data-dropdown-toggle="user-dropdown" class="flex items-center gap-3 cursor-pointer">
+            <div class="w-8 h-8 bg-gray-900 rounded-full flex items-center justify-center">
+              <span class="text-white text-sm font-medium">A</span>
+            </div>
+            <span class="hidden sm:block text-sm font-medium text-gray-700">Admin</span>
+            <svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
+            </svg>
+          </button>
+          <!-- Dropdown -->
+          <div id="user-dropdown" data-dropdown class="hidden absolute right-0 mt-2 w-48 bg-white border border-gray-200 shadow-lg z-50">
+            <a href="settings.html" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-50">设置</a>
+            <a href="auth/login.html" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-50">退出登录</a>
+          </div>
+        </div>
+      </header>
+
+      <!-- Page Content -->
+      <main class="flex-1 p-4 lg:p-8 overflow-auto">
+        <!-- Page Header -->
+        <div class="mb-8">
+          <h2 class="text-2xl lg:text-3xl font-bold text-black">Dashboard</h2>
+          <p class="text-gray-400 mt-1">欢迎回来,这是您的数据概览</p>
+        </div>
+
+        <!-- Stats Cards -->
+        <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 lg:gap-6 mb-8">
+          <!-- Card 1 -->
+          <div class="card">
+            <div class="flex items-center justify-between mb-4">
+              <span class="text-gray-500 text-sm">总用户</span>
+              <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
+              </svg>
+            </div>
+            <div class="text-3xl font-bold text-black">12,456</div>
+            <div class="text-sm text-green-600 mt-2">+12.5% 较上月</div>
+          </div>
+
+          <!-- Card 2 -->
+          <div class="card">
+            <div class="flex items-center justify-between mb-4">
+              <span class="text-gray-500 text-sm">活跃用户</span>
+              <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"/>
+              </svg>
+            </div>
+            <div class="text-3xl font-bold text-black">8,234</div>
+            <div class="text-sm text-green-600 mt-2">+8.2% 较上月</div>
+          </div>
+
+          <!-- Card 3 -->
+          <div class="card">
+            <div class="flex items-center justify-between mb-4">
+              <span class="text-gray-500 text-sm">直播场次</span>
+              <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
+              </svg>
+            </div>
+            <div class="text-3xl font-bold text-black">1,892</div>
+            <div class="text-sm text-green-600 mt-2">+23.1% 较上月</div>
+          </div>
+
+          <!-- Card 4 -->
+          <div class="card">
+            <div class="flex items-center justify-between mb-4">
+              <span class="text-gray-500 text-sm">系统稳定性</span>
+              <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/>
+              </svg>
+            </div>
+            <div class="text-3xl font-bold text-black">99.9%</div>
+            <div class="text-sm text-gray-500 mt-2">正常运行</div>
+          </div>
+        </div>
+
+        <!-- Charts Section -->
+        <div class="grid grid-cols-1 lg:grid-cols-2 gap-4 lg:gap-6 mb-8">
+          <!-- Chart 1 -->
+          <div class="card">
+            <h3 class="text-lg font-semibold text-black mb-4">用户增长趋势</h3>
+            <div class="h-64 bg-gray-50 border border-gray-100 flex items-center justify-center">
+              <span class="text-gray-400">图表区域</span>
+            </div>
+          </div>
+
+          <!-- Chart 2 -->
+          <div class="card">
+            <h3 class="text-lg font-semibold text-black mb-4">直播数据统计</h3>
+            <div class="h-64 bg-gray-50 border border-gray-100 flex items-center justify-center">
+              <span class="text-gray-400">图表区域</span>
+            </div>
+          </div>
+        </div>
+
+        <!-- Recent Activity -->
+        <div class="card">
+          <div class="flex items-center justify-between mb-6">
+            <h3 class="text-lg font-semibold text-black">最近活动</h3>
+            <a href="users.html" class="text-sm text-gray-500 hover:text-black">查看全部</a>
+          </div>
+          <div class="space-y-4">
+            <div class="flex items-center gap-4 py-3 border-b border-gray-100">
+              <div class="w-10 h-10 bg-gray-900 rounded-full flex items-center justify-center flex-shrink-0">
+                <span class="text-white text-sm font-medium">张</span>
+              </div>
+              <div class="flex-1 min-w-0">
+                <p class="text-sm font-medium text-black truncate">张明 开始了新直播</p>
+                <p class="text-xs text-gray-400">2 分钟前</p>
+              </div>
+              <span class="badge badge-success">直播中</span>
+            </div>
+            <div class="flex items-center gap-4 py-3 border-b border-gray-100">
+              <div class="w-10 h-10 bg-gray-900 rounded-full flex items-center justify-center flex-shrink-0">
+                <span class="text-white text-sm font-medium">李</span>
+              </div>
+              <div class="flex-1 min-w-0">
+                <p class="text-sm font-medium text-black truncate">李华 注册了新账户</p>
+                <p class="text-xs text-gray-400">15 分钟前</p>
+              </div>
+              <span class="badge bg-gray-100 text-gray-800">新用户</span>
+            </div>
+            <div class="flex items-center gap-4 py-3">
+              <div class="w-10 h-10 bg-gray-900 rounded-full flex items-center justify-center flex-shrink-0">
+                <span class="text-white text-sm font-medium">王</span>
+              </div>
+              <div class="flex-1 min-w-0">
+                <p class="text-sm font-medium text-black truncate">王芳 结束了直播</p>
+                <p class="text-xs text-gray-400">1 小时前</p>
+              </div>
+              <span class="badge bg-gray-100 text-gray-800">已结束</span>
+            </div>
+          </div>
+        </div>
+      </main>
+    </div>
+  </div>
+
+  <script type="module" src="../src/js/main.js"></script>
+</body>
+</html>

+ 197 - 0
Prototype/admin-template/pages/settings.html

@@ -0,0 +1,197 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>设置 - TG Admin</title>
+  <link rel="stylesheet" href="../src/styles/main.css">
+</head>
+<body class="min-h-screen bg-gray-50">
+  <div class="flex min-h-screen">
+
+    <!-- Sidebar Overlay (Mobile) -->
+    <div id="sidebar-overlay" class="fixed inset-0 bg-black/50 z-40 lg:hidden hidden"></div>
+
+    <!-- Sidebar -->
+    <aside id="sidebar" class="fixed lg:static inset-y-0 left-0 z-50 w-64 bg-gradient-to-b from-gray-900 via-gray-900 to-black transform -translate-x-full lg:translate-x-0 transition-transform duration-200">
+      <div class="flex flex-col h-full">
+        <div class="flex items-center justify-between h-16 px-6 border-b border-white/10">
+          <div class="flex items-center gap-3">
+            <div class="w-8 h-8 bg-white rounded-lg flex items-center justify-center">
+              <svg class="w-4 h-4 text-black" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
+              </svg>
+            </div>
+            <span class="text-white font-semibold">TG Admin</span>
+          </div>
+          <button id="sidebar-close" class="lg:hidden text-gray-400 hover:text-white">
+            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
+            </svg>
+          </button>
+        </div>
+        <nav class="flex-1 px-4 py-6 space-y-1 overflow-y-auto">
+          <a href="dashboard.html" class="nav-item">
+            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
+            </svg>
+            <span>Dashboard</span>
+          </a>
+          <a href="users.html" class="nav-item">
+            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z"/>
+            </svg>
+            <span>用户管理</span>
+          </a>
+          <a href="settings.html" class="nav-item active">
+            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
+            </svg>
+            <span>设置</span>
+          </a>
+        </nav>
+        <div class="px-6 py-4 border-t border-white/10">
+          <a href="auth/login.html" class="flex items-center gap-3 text-gray-400 hover:text-white transition-colors">
+            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>
+            </svg>
+            <span>退出登录</span>
+          </a>
+        </div>
+      </div>
+    </aside>
+
+    <!-- Main Content -->
+    <div class="flex-1 flex flex-col min-w-0">
+      <!-- Header -->
+      <header class="h-16 bg-white border-b border-gray-200 flex items-center justify-between px-4 lg:px-8">
+        <button id="sidebar-toggle" class="lg:hidden p-2 -ml-2 text-gray-600 hover:text-black">
+          <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
+          </svg>
+        </button>
+        <h1 class="text-lg font-semibold text-black hidden lg:block">设置</h1>
+        <div class="relative">
+          <button data-dropdown-toggle="user-dropdown" class="flex items-center gap-3 cursor-pointer">
+            <div class="w-8 h-8 bg-gray-900 rounded-full flex items-center justify-center">
+              <span class="text-white text-sm font-medium">A</span>
+            </div>
+            <span class="hidden sm:block text-sm font-medium text-gray-700">Admin</span>
+          </button>
+          <div id="user-dropdown" data-dropdown class="hidden absolute right-0 mt-2 w-48 bg-white border border-gray-200 shadow-lg z-50">
+            <a href="settings.html" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-50">设置</a>
+            <a href="auth/login.html" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-50">退出登录</a>
+          </div>
+        </div>
+      </header>
+
+      <!-- Page Content -->
+      <main class="flex-1 p-4 lg:p-8 overflow-auto">
+        <div class="mb-8">
+          <h2 class="text-2xl lg:text-3xl font-bold text-black">设置</h2>
+          <p class="text-gray-400 mt-1">管理您的账户和系统设置</p>
+        </div>
+
+        <div class="max-w-2xl space-y-6">
+          <!-- Profile Section -->
+          <div class="card">
+            <h3 class="text-lg font-semibold text-black mb-6">个人资料</h3>
+            <form class="space-y-6">
+              <div class="flex items-center gap-6 pb-6 border-b border-gray-100">
+                <div class="w-20 h-20 bg-gray-900 rounded-full flex items-center justify-center flex-shrink-0">
+                  <span class="text-white text-2xl font-medium">A</span>
+                </div>
+                <div>
+                  <button type="button" class="btn-secondary w-auto px-4 py-2 text-sm">更换头像</button>
+                  <p class="text-xs text-gray-400 mt-2">支持 JPG、PNG 格式,最大 2MB</p>
+                </div>
+              </div>
+              <div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
+                <div>
+                  <label class="block text-sm font-medium text-gray-700 mb-2">用户名</label>
+                  <input type="text" value="Admin" class="input-box">
+                </div>
+                <div>
+                  <label class="block text-sm font-medium text-gray-700 mb-2">邮箱</label>
+                  <input type="email" value="admin@example.com" class="input-box">
+                </div>
+              </div>
+              <div>
+                <label class="block text-sm font-medium text-gray-700 mb-2">个人简介</label>
+                <textarea rows="3" class="input-box" placeholder="介绍一下自己..."></textarea>
+              </div>
+              <div class="flex justify-end">
+                <button type="submit" class="btn-primary w-auto px-6">保存更改</button>
+              </div>
+            </form>
+          </div>
+
+          <!-- Security Section -->
+          <div class="card">
+            <h3 class="text-lg font-semibold text-black mb-6">安全设置</h3>
+            <form class="space-y-6">
+              <div>
+                <label class="block text-sm font-medium text-gray-700 mb-2">当前密码</label>
+                <input type="password" placeholder="输入当前密码" class="input-box">
+              </div>
+              <div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
+                <div>
+                  <label class="block text-sm font-medium text-gray-700 mb-2">新密码</label>
+                  <input type="password" placeholder="输入新密码" class="input-box">
+                </div>
+                <div>
+                  <label class="block text-sm font-medium text-gray-700 mb-2">确认新密码</label>
+                  <input type="password" placeholder="再次输入新密码" class="input-box">
+                </div>
+              </div>
+              <div class="flex justify-end">
+                <button type="submit" class="btn-primary w-auto px-6">更新密码</button>
+              </div>
+            </form>
+          </div>
+
+          <!-- Notification Section -->
+          <div class="card">
+            <h3 class="text-lg font-semibold text-black mb-6">通知设置</h3>
+            <div class="space-y-4">
+              <label class="flex items-center justify-between cursor-pointer group">
+                <div>
+                  <div class="font-medium text-gray-900">邮件通知</div>
+                  <div class="text-sm text-gray-400">接收系统通知和更新</div>
+                </div>
+                <input type="checkbox" checked class="w-5 h-5 rounded border-gray-300 text-black focus:ring-black cursor-pointer">
+              </label>
+              <label class="flex items-center justify-between cursor-pointer group border-t border-gray-100 pt-4">
+                <div>
+                  <div class="font-medium text-gray-900">安全提醒</div>
+                  <div class="text-sm text-gray-400">登录异常时发送提醒</div>
+                </div>
+                <input type="checkbox" checked class="w-5 h-5 rounded border-gray-300 text-black focus:ring-black cursor-pointer">
+              </label>
+              <label class="flex items-center justify-between cursor-pointer group border-t border-gray-100 pt-4">
+                <div>
+                  <div class="font-medium text-gray-900">营销邮件</div>
+                  <div class="text-sm text-gray-400">接收产品更新和优惠信息</div>
+                </div>
+                <input type="checkbox" class="w-5 h-5 rounded border-gray-300 text-black focus:ring-black cursor-pointer">
+              </label>
+            </div>
+          </div>
+
+          <!-- Danger Zone -->
+          <div class="card border-red-200">
+            <h3 class="text-lg font-semibold text-red-600 mb-4">危险区域</h3>
+            <p class="text-sm text-gray-600 mb-4">删除账户后,所有数据将被永久清除,此操作不可撤销。</p>
+            <button class="px-4 py-2 border border-red-600 text-red-600 hover:bg-red-50 transition-colors text-sm font-medium">
+              删除账户
+            </button>
+          </div>
+        </div>
+      </main>
+    </div>
+  </div>
+
+  <script type="module" src="../src/js/main.js"></script>
+</body>
+</html>

+ 301 - 0
Prototype/admin-template/pages/users.html

@@ -0,0 +1,301 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>用户管理 - TG Admin</title>
+  <script src="https://cdn.tailwindcss.com"></script>
+  <script>
+    tailwind.config = {
+      theme: {
+        extend: {
+          fontFamily: { sans: ['Inter', 'system-ui', 'sans-serif'] },
+          colors: { brand: { DEFAULT: '#000000', hover: '#1a1a1a' } }
+        }
+      }
+    }
+  </script>
+  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
+  <style>
+    body { font-family: 'Inter', system-ui, sans-serif; }
+    .nav-item { @apply flex items-center gap-3 px-4 py-3 text-gray-400 hover:text-white hover:bg-white/5 transition-colors cursor-pointer; }
+    .nav-item.active { @apply text-white bg-white/10; }
+    .card { @apply bg-white border border-gray-200 p-6; }
+    .btn-primary { @apply py-3.5 bg-black hover:bg-gray-900 text-white font-semibold rounded-none transition-colors cursor-pointer; }
+    .btn-secondary { @apply py-3 border border-gray-200 hover:border-black hover:bg-gray-50 text-black font-medium rounded-none transition-colors cursor-pointer; }
+    .input-box { @apply w-full px-4 py-3 bg-gray-50 border border-gray-200 text-black placeholder-gray-400 focus:outline-none focus:border-black focus:bg-white transition-colors; }
+    .badge { @apply inline-flex items-center px-2.5 py-0.5 text-xs font-medium; }
+    .badge-success { @apply bg-green-100 text-green-800; }
+    .badge-danger { @apply bg-red-100 text-red-800; }
+    .table-header { @apply text-left text-xs font-semibold text-gray-500 uppercase tracking-wider; }
+    .table-cell { @apply py-4 text-sm text-gray-900; }
+    .table-row { @apply border-b border-gray-100 hover:bg-gray-50 transition-colors; }
+  </style>
+</head>
+<body class="min-h-screen bg-gray-50">
+  <div class="flex min-h-screen">
+
+    <!-- Sidebar Overlay (Mobile) -->
+    <div id="sidebar-overlay" class="fixed inset-0 bg-black/50 z-40 lg:hidden hidden"></div>
+
+    <!-- Sidebar -->
+    <aside id="sidebar" class="fixed lg:static inset-y-0 left-0 z-50 w-64 bg-gradient-to-b from-gray-900 via-gray-900 to-black transform -translate-x-full lg:translate-x-0 transition-transform duration-200">
+      <div class="flex flex-col h-full">
+        <!-- Logo -->
+        <div class="flex items-center justify-between h-16 px-6 border-b border-white/10">
+          <div class="flex items-center gap-3">
+            <div class="w-8 h-8 bg-white rounded-lg flex items-center justify-center">
+              <svg class="w-4 h-4 text-black" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
+              </svg>
+            </div>
+            <span class="text-white font-semibold">TG Admin</span>
+          </div>
+          <button id="sidebar-close" class="lg:hidden text-gray-400 hover:text-white">
+            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
+            </svg>
+          </button>
+        </div>
+
+        <!-- Navigation -->
+        <nav class="flex-1 px-4 py-6 space-y-1 overflow-y-auto">
+          <a href="dashboard.html" class="nav-item">
+            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
+            </svg>
+            <span>Dashboard</span>
+          </a>
+          <a href="users.html" class="nav-item active">
+            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z"/>
+            </svg>
+            <span>用户管理</span>
+          </a>
+          <a href="settings.html" class="nav-item">
+            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
+            </svg>
+            <span>设置</span>
+          </a>
+        </nav>
+
+        <!-- Footer -->
+        <div class="px-6 py-4 border-t border-white/10">
+          <a href="auth/login.html" class="flex items-center gap-3 text-gray-400 hover:text-white transition-colors">
+            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>
+            </svg>
+            <span>退出登录</span>
+          </a>
+        </div>
+      </div>
+    </aside>
+
+    <!-- Main Content -->
+    <div class="flex-1 flex flex-col min-w-0">
+      <!-- Header -->
+      <header class="h-16 bg-white border-b border-gray-200 flex items-center justify-between px-4 lg:px-8">
+        <button id="sidebar-toggle" class="lg:hidden p-2 -ml-2 text-gray-600 hover:text-black">
+          <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
+          </svg>
+        </button>
+        <h1 class="text-lg font-semibold text-black hidden lg:block">用户管理</h1>
+        <div class="relative">
+          <button data-dropdown-toggle="user-dropdown" class="flex items-center gap-3 cursor-pointer">
+            <div class="w-8 h-8 bg-gray-900 rounded-full flex items-center justify-center">
+              <span class="text-white text-sm font-medium">A</span>
+            </div>
+            <span class="hidden sm:block text-sm font-medium text-gray-700">Admin</span>
+          </button>
+          <div id="user-dropdown" data-dropdown class="hidden absolute right-0 mt-2 w-48 bg-white border border-gray-200 shadow-lg z-50">
+            <a href="settings.html" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-50">设置</a>
+            <a href="auth/login.html" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-50">退出登录</a>
+          </div>
+        </div>
+      </header>
+
+      <!-- Page Content -->
+      <main class="flex-1 p-4 lg:p-8 overflow-auto">
+        <!-- Page Header -->
+        <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-8">
+          <div>
+            <h2 class="text-2xl lg:text-3xl font-bold text-black">用户管理</h2>
+            <p class="text-gray-400 mt-1">管理系统用户和权限</p>
+          </div>
+          <button class="btn-primary w-auto px-6 flex items-center gap-2">
+            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
+            </svg>
+            添加用户
+          </button>
+        </div>
+
+        <!-- Search and Filters -->
+        <div class="card mb-6">
+          <div class="flex flex-col sm:flex-row gap-4">
+            <div class="flex-1">
+              <div class="relative">
+                <svg class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
+                </svg>
+                <input type="text" placeholder="搜索用户..." class="input-box pl-10">
+              </div>
+            </div>
+            <select class="input-box w-full sm:w-40">
+              <option>全部状态</option>
+              <option>活跃</option>
+              <option>禁用</option>
+            </select>
+            <select class="input-box w-full sm:w-40">
+              <option>全部角色</option>
+              <option>管理员</option>
+              <option>主播</option>
+              <option>观众</option>
+            </select>
+          </div>
+        </div>
+
+        <!-- Users Table -->
+        <div class="card overflow-hidden">
+          <div class="overflow-x-auto">
+            <table class="w-full">
+              <thead>
+                <tr class="border-b border-gray-200">
+                  <th class="table-header px-4 py-3">用户</th>
+                  <th class="table-header px-4 py-3 hidden sm:table-cell">邮箱</th>
+                  <th class="table-header px-4 py-3 hidden md:table-cell">角色</th>
+                  <th class="table-header px-4 py-3">状态</th>
+                  <th class="table-header px-4 py-3 hidden lg:table-cell">注册时间</th>
+                  <th class="table-header px-4 py-3 text-right">操作</th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr class="table-row">
+                  <td class="table-cell px-4">
+                    <div class="flex items-center gap-3">
+                      <div class="w-10 h-10 bg-gray-900 rounded-full flex items-center justify-center flex-shrink-0">
+                        <span class="text-white text-sm font-medium">张</span>
+                      </div>
+                      <div>
+                        <div class="font-medium">张明</div>
+                        <div class="text-xs text-gray-400 sm:hidden">zhang@example.com</div>
+                      </div>
+                    </div>
+                  </td>
+                  <td class="table-cell px-4 hidden sm:table-cell text-gray-600">zhang@example.com</td>
+                  <td class="table-cell px-4 hidden md:table-cell">
+                    <span class="badge bg-purple-100 text-purple-800">主播</span>
+                  </td>
+                  <td class="table-cell px-4">
+                    <span class="badge badge-success">活跃</span>
+                  </td>
+                  <td class="table-cell px-4 hidden lg:table-cell text-gray-500">2024-01-15</td>
+                  <td class="table-cell px-4 text-right">
+                    <div class="flex items-center justify-end gap-2">
+                      <button class="p-2 text-gray-400 hover:text-black transition-colors" title="编辑">
+                        <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
+                        </svg>
+                      </button>
+                      <button onclick="confirmDelete('确定要删除用户 张明 吗?')" class="p-2 text-gray-400 hover:text-red-600 transition-colors" title="删除">
+                        <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
+                        </svg>
+                      </button>
+                    </div>
+                  </td>
+                </tr>
+                <tr class="table-row">
+                  <td class="table-cell px-4">
+                    <div class="flex items-center gap-3">
+                      <div class="w-10 h-10 bg-gray-900 rounded-full flex items-center justify-center flex-shrink-0">
+                        <span class="text-white text-sm font-medium">李</span>
+                      </div>
+                      <div>
+                        <div class="font-medium">李华</div>
+                        <div class="text-xs text-gray-400 sm:hidden">lihua@example.com</div>
+                      </div>
+                    </div>
+                  </td>
+                  <td class="table-cell px-4 hidden sm:table-cell text-gray-600">lihua@example.com</td>
+                  <td class="table-cell px-4 hidden md:table-cell">
+                    <span class="badge bg-blue-100 text-blue-800">管理员</span>
+                  </td>
+                  <td class="table-cell px-4">
+                    <span class="badge badge-success">活跃</span>
+                  </td>
+                  <td class="table-cell px-4 hidden lg:table-cell text-gray-500">2024-01-10</td>
+                  <td class="table-cell px-4 text-right">
+                    <div class="flex items-center justify-end gap-2">
+                      <button class="p-2 text-gray-400 hover:text-black transition-colors" title="编辑">
+                        <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
+                        </svg>
+                      </button>
+                      <button onclick="confirmDelete('确定要删除用户 李华 吗?')" class="p-2 text-gray-400 hover:text-red-600 transition-colors" title="删除">
+                        <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
+                        </svg>
+                      </button>
+                    </div>
+                  </td>
+                </tr>
+                <tr class="table-row">
+                  <td class="table-cell px-4">
+                    <div class="flex items-center gap-3">
+                      <div class="w-10 h-10 bg-gray-900 rounded-full flex items-center justify-center flex-shrink-0">
+                        <span class="text-white text-sm font-medium">王</span>
+                      </div>
+                      <div>
+                        <div class="font-medium">王芳</div>
+                        <div class="text-xs text-gray-400 sm:hidden">wangfang@example.com</div>
+                      </div>
+                    </div>
+                  </td>
+                  <td class="table-cell px-4 hidden sm:table-cell text-gray-600">wangfang@example.com</td>
+                  <td class="table-cell px-4 hidden md:table-cell">
+                    <span class="badge bg-gray-100 text-gray-800">观众</span>
+                  </td>
+                  <td class="table-cell px-4">
+                    <span class="badge badge-danger">禁用</span>
+                  </td>
+                  <td class="table-cell px-4 hidden lg:table-cell text-gray-500">2024-01-05</td>
+                  <td class="table-cell px-4 text-right">
+                    <div class="flex items-center justify-end gap-2">
+                      <button class="p-2 text-gray-400 hover:text-black transition-colors" title="编辑">
+                        <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
+                        </svg>
+                      </button>
+                      <button onclick="confirmDelete('确定要删除用户 王芳 吗?')" class="p-2 text-gray-400 hover:text-red-600 transition-colors" title="删除">
+                        <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
+                        </svg>
+                      </button>
+                    </div>
+                  </td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+
+          <!-- Pagination -->
+          <div class="flex flex-col sm:flex-row items-center justify-between gap-4 px-4 py-4 border-t border-gray-200">
+            <p class="text-sm text-gray-500">显示 1-3 共 3 条</p>
+            <div class="flex items-center gap-2">
+              <button class="px-3 py-1.5 border border-gray-200 text-sm text-gray-400 cursor-not-allowed">上一页</button>
+              <button class="px-3 py-1.5 bg-black text-white text-sm">1</button>
+              <button class="px-3 py-1.5 border border-gray-200 text-sm text-gray-400 cursor-not-allowed">下一页</button>
+            </div>
+          </div>
+        </div>
+      </main>
+    </div>
+  </div>
+
+  <script type="module" src="../src/js/main.js"></script>
+</body>
+</html>

+ 1195 - 0
Prototype/admin-template/pnpm-lock.yaml

@@ -0,0 +1,1195 @@
+lockfileVersion: '9.0'
+
+settings:
+  autoInstallPeers: true
+  excludeLinksFromLockfile: false
+
+importers:
+
+  .:
+    devDependencies:
+      autoprefixer:
+        specifier: ^10.4.0
+        version: 10.4.23(postcss@8.5.6)
+      postcss:
+        specifier: ^8.4.0
+        version: 8.5.6
+      tailwindcss:
+        specifier: ^3.4.0
+        version: 3.4.19
+      vite:
+        specifier: ^5.4.0
+        version: 5.4.21
+
+packages:
+
+  '@alloc/quick-lru@5.2.0':
+    resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
+    engines: {node: '>=10'}
+
+  '@esbuild/aix-ppc64@0.21.5':
+    resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
+    engines: {node: '>=12'}
+    cpu: [ppc64]
+    os: [aix]
+
+  '@esbuild/android-arm64@0.21.5':
+    resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [android]
+
+  '@esbuild/android-arm@0.21.5':
+    resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
+    engines: {node: '>=12'}
+    cpu: [arm]
+    os: [android]
+
+  '@esbuild/android-x64@0.21.5':
+    resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [android]
+
+  '@esbuild/darwin-arm64@0.21.5':
+    resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [darwin]
+
+  '@esbuild/darwin-x64@0.21.5':
+    resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [darwin]
+
+  '@esbuild/freebsd-arm64@0.21.5':
+    resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [freebsd]
+
+  '@esbuild/freebsd-x64@0.21.5':
+    resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [freebsd]
+
+  '@esbuild/linux-arm64@0.21.5':
+    resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [linux]
+
+  '@esbuild/linux-arm@0.21.5':
+    resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
+    engines: {node: '>=12'}
+    cpu: [arm]
+    os: [linux]
+
+  '@esbuild/linux-ia32@0.21.5':
+    resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
+    engines: {node: '>=12'}
+    cpu: [ia32]
+    os: [linux]
+
+  '@esbuild/linux-loong64@0.21.5':
+    resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
+    engines: {node: '>=12'}
+    cpu: [loong64]
+    os: [linux]
+
+  '@esbuild/linux-mips64el@0.21.5':
+    resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
+    engines: {node: '>=12'}
+    cpu: [mips64el]
+    os: [linux]
+
+  '@esbuild/linux-ppc64@0.21.5':
+    resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
+    engines: {node: '>=12'}
+    cpu: [ppc64]
+    os: [linux]
+
+  '@esbuild/linux-riscv64@0.21.5':
+    resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
+    engines: {node: '>=12'}
+    cpu: [riscv64]
+    os: [linux]
+
+  '@esbuild/linux-s390x@0.21.5':
+    resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
+    engines: {node: '>=12'}
+    cpu: [s390x]
+    os: [linux]
+
+  '@esbuild/linux-x64@0.21.5':
+    resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [linux]
+
+  '@esbuild/netbsd-x64@0.21.5':
+    resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [netbsd]
+
+  '@esbuild/openbsd-x64@0.21.5':
+    resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [openbsd]
+
+  '@esbuild/sunos-x64@0.21.5':
+    resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [sunos]
+
+  '@esbuild/win32-arm64@0.21.5':
+    resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [win32]
+
+  '@esbuild/win32-ia32@0.21.5':
+    resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
+    engines: {node: '>=12'}
+    cpu: [ia32]
+    os: [win32]
+
+  '@esbuild/win32-x64@0.21.5':
+    resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [win32]
+
+  '@jridgewell/gen-mapping@0.3.13':
+    resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+  '@jridgewell/resolve-uri@3.1.2':
+    resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+    engines: {node: '>=6.0.0'}
+
+  '@jridgewell/sourcemap-codec@1.5.5':
+    resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+  '@jridgewell/trace-mapping@0.3.31':
+    resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+  '@nodelib/fs.scandir@2.1.5':
+    resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+    engines: {node: '>= 8'}
+
+  '@nodelib/fs.stat@2.0.5':
+    resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+    engines: {node: '>= 8'}
+
+  '@nodelib/fs.walk@1.2.8':
+    resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+    engines: {node: '>= 8'}
+
+  '@rollup/rollup-android-arm-eabi@4.55.1':
+    resolution: {integrity: sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==}
+    cpu: [arm]
+    os: [android]
+
+  '@rollup/rollup-android-arm64@4.55.1':
+    resolution: {integrity: sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==}
+    cpu: [arm64]
+    os: [android]
+
+  '@rollup/rollup-darwin-arm64@4.55.1':
+    resolution: {integrity: sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==}
+    cpu: [arm64]
+    os: [darwin]
+
+  '@rollup/rollup-darwin-x64@4.55.1':
+    resolution: {integrity: sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==}
+    cpu: [x64]
+    os: [darwin]
+
+  '@rollup/rollup-freebsd-arm64@4.55.1':
+    resolution: {integrity: sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==}
+    cpu: [arm64]
+    os: [freebsd]
+
+  '@rollup/rollup-freebsd-x64@4.55.1':
+    resolution: {integrity: sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==}
+    cpu: [x64]
+    os: [freebsd]
+
+  '@rollup/rollup-linux-arm-gnueabihf@4.55.1':
+    resolution: {integrity: sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==}
+    cpu: [arm]
+    os: [linux]
+
+  '@rollup/rollup-linux-arm-musleabihf@4.55.1':
+    resolution: {integrity: sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==}
+    cpu: [arm]
+    os: [linux]
+
+  '@rollup/rollup-linux-arm64-gnu@4.55.1':
+    resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==}
+    cpu: [arm64]
+    os: [linux]
+
+  '@rollup/rollup-linux-arm64-musl@4.55.1':
+    resolution: {integrity: sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==}
+    cpu: [arm64]
+    os: [linux]
+
+  '@rollup/rollup-linux-loong64-gnu@4.55.1':
+    resolution: {integrity: sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==}
+    cpu: [loong64]
+    os: [linux]
+
+  '@rollup/rollup-linux-loong64-musl@4.55.1':
+    resolution: {integrity: sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==}
+    cpu: [loong64]
+    os: [linux]
+
+  '@rollup/rollup-linux-ppc64-gnu@4.55.1':
+    resolution: {integrity: sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==}
+    cpu: [ppc64]
+    os: [linux]
+
+  '@rollup/rollup-linux-ppc64-musl@4.55.1':
+    resolution: {integrity: sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==}
+    cpu: [ppc64]
+    os: [linux]
+
+  '@rollup/rollup-linux-riscv64-gnu@4.55.1':
+    resolution: {integrity: sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==}
+    cpu: [riscv64]
+    os: [linux]
+
+  '@rollup/rollup-linux-riscv64-musl@4.55.1':
+    resolution: {integrity: sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==}
+    cpu: [riscv64]
+    os: [linux]
+
+  '@rollup/rollup-linux-s390x-gnu@4.55.1':
+    resolution: {integrity: sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==}
+    cpu: [s390x]
+    os: [linux]
+
+  '@rollup/rollup-linux-x64-gnu@4.55.1':
+    resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==}
+    cpu: [x64]
+    os: [linux]
+
+  '@rollup/rollup-linux-x64-musl@4.55.1':
+    resolution: {integrity: sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==}
+    cpu: [x64]
+    os: [linux]
+
+  '@rollup/rollup-openbsd-x64@4.55.1':
+    resolution: {integrity: sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==}
+    cpu: [x64]
+    os: [openbsd]
+
+  '@rollup/rollup-openharmony-arm64@4.55.1':
+    resolution: {integrity: sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==}
+    cpu: [arm64]
+    os: [openharmony]
+
+  '@rollup/rollup-win32-arm64-msvc@4.55.1':
+    resolution: {integrity: sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==}
+    cpu: [arm64]
+    os: [win32]
+
+  '@rollup/rollup-win32-ia32-msvc@4.55.1':
+    resolution: {integrity: sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==}
+    cpu: [ia32]
+    os: [win32]
+
+  '@rollup/rollup-win32-x64-gnu@4.55.1':
+    resolution: {integrity: sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==}
+    cpu: [x64]
+    os: [win32]
+
+  '@rollup/rollup-win32-x64-msvc@4.55.1':
+    resolution: {integrity: sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==}
+    cpu: [x64]
+    os: [win32]
+
+  '@types/estree@1.0.8':
+    resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+  any-promise@1.3.0:
+    resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
+
+  anymatch@3.1.3:
+    resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+    engines: {node: '>= 8'}
+
+  arg@5.0.2:
+    resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
+
+  autoprefixer@10.4.23:
+    resolution: {integrity: sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==}
+    engines: {node: ^10 || ^12 || >=14}
+    hasBin: true
+    peerDependencies:
+      postcss: ^8.1.0
+
+  baseline-browser-mapping@2.9.14:
+    resolution: {integrity: sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==}
+    hasBin: true
+
+  binary-extensions@2.3.0:
+    resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
+    engines: {node: '>=8'}
+
+  braces@3.0.3:
+    resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+    engines: {node: '>=8'}
+
+  browserslist@4.28.1:
+    resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==}
+    engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+    hasBin: true
+
+  camelcase-css@2.0.1:
+    resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
+    engines: {node: '>= 6'}
+
+  caniuse-lite@1.0.30001763:
+    resolution: {integrity: sha512-mh/dGtq56uN98LlNX9qdbKnzINhX0QzhiWBFEkFfsFO4QyCvL8YegrJAazCwXIeqkIob8BlZPGM3xdnY+sgmvQ==}
+
+  chokidar@3.6.0:
+    resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
+    engines: {node: '>= 8.10.0'}
+
+  commander@4.1.1:
+    resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
+    engines: {node: '>= 6'}
+
+  cssesc@3.0.0:
+    resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
+    engines: {node: '>=4'}
+    hasBin: true
+
+  didyoumean@1.2.2:
+    resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
+
+  dlv@1.1.3:
+    resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
+
+  electron-to-chromium@1.5.267:
+    resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==}
+
+  esbuild@0.21.5:
+    resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
+    engines: {node: '>=12'}
+    hasBin: true
+
+  escalade@3.2.0:
+    resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+    engines: {node: '>=6'}
+
+  fast-glob@3.3.3:
+    resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
+    engines: {node: '>=8.6.0'}
+
+  fastq@1.20.1:
+    resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
+
+  fdir@6.5.0:
+    resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+    engines: {node: '>=12.0.0'}
+    peerDependencies:
+      picomatch: ^3 || ^4
+    peerDependenciesMeta:
+      picomatch:
+        optional: true
+
+  fill-range@7.1.1:
+    resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+    engines: {node: '>=8'}
+
+  fraction.js@5.3.4:
+    resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
+
+  fsevents@2.3.3:
+    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+    os: [darwin]
+
+  function-bind@1.1.2:
+    resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+  glob-parent@5.1.2:
+    resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+    engines: {node: '>= 6'}
+
+  glob-parent@6.0.2:
+    resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+    engines: {node: '>=10.13.0'}
+
+  hasown@2.0.2:
+    resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+    engines: {node: '>= 0.4'}
+
+  is-binary-path@2.1.0:
+    resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
+    engines: {node: '>=8'}
+
+  is-core-module@2.16.1:
+    resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
+    engines: {node: '>= 0.4'}
+
+  is-extglob@2.1.1:
+    resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+    engines: {node: '>=0.10.0'}
+
+  is-glob@4.0.3:
+    resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+    engines: {node: '>=0.10.0'}
+
+  is-number@7.0.0:
+    resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+    engines: {node: '>=0.12.0'}
+
+  jiti@1.21.7:
+    resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
+    hasBin: true
+
+  lilconfig@3.1.3:
+    resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
+    engines: {node: '>=14'}
+
+  lines-and-columns@1.2.4:
+    resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+
+  merge2@1.4.1:
+    resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+    engines: {node: '>= 8'}
+
+  micromatch@4.0.8:
+    resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+    engines: {node: '>=8.6'}
+
+  mz@2.7.0:
+    resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
+
+  nanoid@3.3.11:
+    resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+    engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+    hasBin: true
+
+  node-releases@2.0.27:
+    resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
+
+  normalize-path@3.0.0:
+    resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+    engines: {node: '>=0.10.0'}
+
+  object-assign@4.1.1:
+    resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+    engines: {node: '>=0.10.0'}
+
+  object-hash@3.0.0:
+    resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
+    engines: {node: '>= 6'}
+
+  path-parse@1.0.7:
+    resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+  picocolors@1.1.1:
+    resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+  picomatch@2.3.1:
+    resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+    engines: {node: '>=8.6'}
+
+  picomatch@4.0.3:
+    resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
+    engines: {node: '>=12'}
+
+  pify@2.3.0:
+    resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
+    engines: {node: '>=0.10.0'}
+
+  pirates@4.0.7:
+    resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
+    engines: {node: '>= 6'}
+
+  postcss-import@15.1.0:
+    resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
+    engines: {node: '>=14.0.0'}
+    peerDependencies:
+      postcss: ^8.0.0
+
+  postcss-js@4.1.0:
+    resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==}
+    engines: {node: ^12 || ^14 || >= 16}
+    peerDependencies:
+      postcss: ^8.4.21
+
+  postcss-load-config@6.0.1:
+    resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==}
+    engines: {node: '>= 18'}
+    peerDependencies:
+      jiti: '>=1.21.0'
+      postcss: '>=8.0.9'
+      tsx: ^4.8.1
+      yaml: ^2.4.2
+    peerDependenciesMeta:
+      jiti:
+        optional: true
+      postcss:
+        optional: true
+      tsx:
+        optional: true
+      yaml:
+        optional: true
+
+  postcss-nested@6.2.0:
+    resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==}
+    engines: {node: '>=12.0'}
+    peerDependencies:
+      postcss: ^8.2.14
+
+  postcss-selector-parser@6.1.2:
+    resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
+    engines: {node: '>=4'}
+
+  postcss-value-parser@4.2.0:
+    resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+
+  postcss@8.5.6:
+    resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
+    engines: {node: ^10 || ^12 || >=14}
+
+  queue-microtask@1.2.3:
+    resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
+  read-cache@1.0.0:
+    resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
+
+  readdirp@3.6.0:
+    resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+    engines: {node: '>=8.10.0'}
+
+  resolve@1.22.11:
+    resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
+    engines: {node: '>= 0.4'}
+    hasBin: true
+
+  reusify@1.1.0:
+    resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
+    engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
+  rollup@4.55.1:
+    resolution: {integrity: sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==}
+    engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+    hasBin: true
+
+  run-parallel@1.2.0:
+    resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+
+  source-map-js@1.2.1:
+    resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+    engines: {node: '>=0.10.0'}
+
+  sucrase@3.35.1:
+    resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==}
+    engines: {node: '>=16 || 14 >=14.17'}
+    hasBin: true
+
+  supports-preserve-symlinks-flag@1.0.0:
+    resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+    engines: {node: '>= 0.4'}
+
+  tailwindcss@3.4.19:
+    resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==}
+    engines: {node: '>=14.0.0'}
+    hasBin: true
+
+  thenify-all@1.6.0:
+    resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
+    engines: {node: '>=0.8'}
+
+  thenify@3.3.1:
+    resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
+
+  tinyglobby@0.2.15:
+    resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
+    engines: {node: '>=12.0.0'}
+
+  to-regex-range@5.0.1:
+    resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+    engines: {node: '>=8.0'}
+
+  ts-interface-checker@0.1.13:
+    resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
+
+  update-browserslist-db@1.2.3:
+    resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
+    hasBin: true
+    peerDependencies:
+      browserslist: '>= 4.21.0'
+
+  util-deprecate@1.0.2:
+    resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
+  vite@5.4.21:
+    resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==}
+    engines: {node: ^18.0.0 || >=20.0.0}
+    hasBin: true
+    peerDependencies:
+      '@types/node': ^18.0.0 || >=20.0.0
+      less: '*'
+      lightningcss: ^1.21.0
+      sass: '*'
+      sass-embedded: '*'
+      stylus: '*'
+      sugarss: '*'
+      terser: ^5.4.0
+    peerDependenciesMeta:
+      '@types/node':
+        optional: true
+      less:
+        optional: true
+      lightningcss:
+        optional: true
+      sass:
+        optional: true
+      sass-embedded:
+        optional: true
+      stylus:
+        optional: true
+      sugarss:
+        optional: true
+      terser:
+        optional: true
+
+snapshots:
+
+  '@alloc/quick-lru@5.2.0': {}
+
+  '@esbuild/aix-ppc64@0.21.5':
+    optional: true
+
+  '@esbuild/android-arm64@0.21.5':
+    optional: true
+
+  '@esbuild/android-arm@0.21.5':
+    optional: true
+
+  '@esbuild/android-x64@0.21.5':
+    optional: true
+
+  '@esbuild/darwin-arm64@0.21.5':
+    optional: true
+
+  '@esbuild/darwin-x64@0.21.5':
+    optional: true
+
+  '@esbuild/freebsd-arm64@0.21.5':
+    optional: true
+
+  '@esbuild/freebsd-x64@0.21.5':
+    optional: true
+
+  '@esbuild/linux-arm64@0.21.5':
+    optional: true
+
+  '@esbuild/linux-arm@0.21.5':
+    optional: true
+
+  '@esbuild/linux-ia32@0.21.5':
+    optional: true
+
+  '@esbuild/linux-loong64@0.21.5':
+    optional: true
+
+  '@esbuild/linux-mips64el@0.21.5':
+    optional: true
+
+  '@esbuild/linux-ppc64@0.21.5':
+    optional: true
+
+  '@esbuild/linux-riscv64@0.21.5':
+    optional: true
+
+  '@esbuild/linux-s390x@0.21.5':
+    optional: true
+
+  '@esbuild/linux-x64@0.21.5':
+    optional: true
+
+  '@esbuild/netbsd-x64@0.21.5':
+    optional: true
+
+  '@esbuild/openbsd-x64@0.21.5':
+    optional: true
+
+  '@esbuild/sunos-x64@0.21.5':
+    optional: true
+
+  '@esbuild/win32-arm64@0.21.5':
+    optional: true
+
+  '@esbuild/win32-ia32@0.21.5':
+    optional: true
+
+  '@esbuild/win32-x64@0.21.5':
+    optional: true
+
+  '@jridgewell/gen-mapping@0.3.13':
+    dependencies:
+      '@jridgewell/sourcemap-codec': 1.5.5
+      '@jridgewell/trace-mapping': 0.3.31
+
+  '@jridgewell/resolve-uri@3.1.2': {}
+
+  '@jridgewell/sourcemap-codec@1.5.5': {}
+
+  '@jridgewell/trace-mapping@0.3.31':
+    dependencies:
+      '@jridgewell/resolve-uri': 3.1.2
+      '@jridgewell/sourcemap-codec': 1.5.5
+
+  '@nodelib/fs.scandir@2.1.5':
+    dependencies:
+      '@nodelib/fs.stat': 2.0.5
+      run-parallel: 1.2.0
+
+  '@nodelib/fs.stat@2.0.5': {}
+
+  '@nodelib/fs.walk@1.2.8':
+    dependencies:
+      '@nodelib/fs.scandir': 2.1.5
+      fastq: 1.20.1
+
+  '@rollup/rollup-android-arm-eabi@4.55.1':
+    optional: true
+
+  '@rollup/rollup-android-arm64@4.55.1':
+    optional: true
+
+  '@rollup/rollup-darwin-arm64@4.55.1':
+    optional: true
+
+  '@rollup/rollup-darwin-x64@4.55.1':
+    optional: true
+
+  '@rollup/rollup-freebsd-arm64@4.55.1':
+    optional: true
+
+  '@rollup/rollup-freebsd-x64@4.55.1':
+    optional: true
+
+  '@rollup/rollup-linux-arm-gnueabihf@4.55.1':
+    optional: true
+
+  '@rollup/rollup-linux-arm-musleabihf@4.55.1':
+    optional: true
+
+  '@rollup/rollup-linux-arm64-gnu@4.55.1':
+    optional: true
+
+  '@rollup/rollup-linux-arm64-musl@4.55.1':
+    optional: true
+
+  '@rollup/rollup-linux-loong64-gnu@4.55.1':
+    optional: true
+
+  '@rollup/rollup-linux-loong64-musl@4.55.1':
+    optional: true
+
+  '@rollup/rollup-linux-ppc64-gnu@4.55.1':
+    optional: true
+
+  '@rollup/rollup-linux-ppc64-musl@4.55.1':
+    optional: true
+
+  '@rollup/rollup-linux-riscv64-gnu@4.55.1':
+    optional: true
+
+  '@rollup/rollup-linux-riscv64-musl@4.55.1':
+    optional: true
+
+  '@rollup/rollup-linux-s390x-gnu@4.55.1':
+    optional: true
+
+  '@rollup/rollup-linux-x64-gnu@4.55.1':
+    optional: true
+
+  '@rollup/rollup-linux-x64-musl@4.55.1':
+    optional: true
+
+  '@rollup/rollup-openbsd-x64@4.55.1':
+    optional: true
+
+  '@rollup/rollup-openharmony-arm64@4.55.1':
+    optional: true
+
+  '@rollup/rollup-win32-arm64-msvc@4.55.1':
+    optional: true
+
+  '@rollup/rollup-win32-ia32-msvc@4.55.1':
+    optional: true
+
+  '@rollup/rollup-win32-x64-gnu@4.55.1':
+    optional: true
+
+  '@rollup/rollup-win32-x64-msvc@4.55.1':
+    optional: true
+
+  '@types/estree@1.0.8': {}
+
+  any-promise@1.3.0: {}
+
+  anymatch@3.1.3:
+    dependencies:
+      normalize-path: 3.0.0
+      picomatch: 2.3.1
+
+  arg@5.0.2: {}
+
+  autoprefixer@10.4.23(postcss@8.5.6):
+    dependencies:
+      browserslist: 4.28.1
+      caniuse-lite: 1.0.30001763
+      fraction.js: 5.3.4
+      picocolors: 1.1.1
+      postcss: 8.5.6
+      postcss-value-parser: 4.2.0
+
+  baseline-browser-mapping@2.9.14: {}
+
+  binary-extensions@2.3.0: {}
+
+  braces@3.0.3:
+    dependencies:
+      fill-range: 7.1.1
+
+  browserslist@4.28.1:
+    dependencies:
+      baseline-browser-mapping: 2.9.14
+      caniuse-lite: 1.0.30001763
+      electron-to-chromium: 1.5.267
+      node-releases: 2.0.27
+      update-browserslist-db: 1.2.3(browserslist@4.28.1)
+
+  camelcase-css@2.0.1: {}
+
+  caniuse-lite@1.0.30001763: {}
+
+  chokidar@3.6.0:
+    dependencies:
+      anymatch: 3.1.3
+      braces: 3.0.3
+      glob-parent: 5.1.2
+      is-binary-path: 2.1.0
+      is-glob: 4.0.3
+      normalize-path: 3.0.0
+      readdirp: 3.6.0
+    optionalDependencies:
+      fsevents: 2.3.3
+
+  commander@4.1.1: {}
+
+  cssesc@3.0.0: {}
+
+  didyoumean@1.2.2: {}
+
+  dlv@1.1.3: {}
+
+  electron-to-chromium@1.5.267: {}
+
+  esbuild@0.21.5:
+    optionalDependencies:
+      '@esbuild/aix-ppc64': 0.21.5
+      '@esbuild/android-arm': 0.21.5
+      '@esbuild/android-arm64': 0.21.5
+      '@esbuild/android-x64': 0.21.5
+      '@esbuild/darwin-arm64': 0.21.5
+      '@esbuild/darwin-x64': 0.21.5
+      '@esbuild/freebsd-arm64': 0.21.5
+      '@esbuild/freebsd-x64': 0.21.5
+      '@esbuild/linux-arm': 0.21.5
+      '@esbuild/linux-arm64': 0.21.5
+      '@esbuild/linux-ia32': 0.21.5
+      '@esbuild/linux-loong64': 0.21.5
+      '@esbuild/linux-mips64el': 0.21.5
+      '@esbuild/linux-ppc64': 0.21.5
+      '@esbuild/linux-riscv64': 0.21.5
+      '@esbuild/linux-s390x': 0.21.5
+      '@esbuild/linux-x64': 0.21.5
+      '@esbuild/netbsd-x64': 0.21.5
+      '@esbuild/openbsd-x64': 0.21.5
+      '@esbuild/sunos-x64': 0.21.5
+      '@esbuild/win32-arm64': 0.21.5
+      '@esbuild/win32-ia32': 0.21.5
+      '@esbuild/win32-x64': 0.21.5
+
+  escalade@3.2.0: {}
+
+  fast-glob@3.3.3:
+    dependencies:
+      '@nodelib/fs.stat': 2.0.5
+      '@nodelib/fs.walk': 1.2.8
+      glob-parent: 5.1.2
+      merge2: 1.4.1
+      micromatch: 4.0.8
+
+  fastq@1.20.1:
+    dependencies:
+      reusify: 1.1.0
+
+  fdir@6.5.0(picomatch@4.0.3):
+    optionalDependencies:
+      picomatch: 4.0.3
+
+  fill-range@7.1.1:
+    dependencies:
+      to-regex-range: 5.0.1
+
+  fraction.js@5.3.4: {}
+
+  fsevents@2.3.3:
+    optional: true
+
+  function-bind@1.1.2: {}
+
+  glob-parent@5.1.2:
+    dependencies:
+      is-glob: 4.0.3
+
+  glob-parent@6.0.2:
+    dependencies:
+      is-glob: 4.0.3
+
+  hasown@2.0.2:
+    dependencies:
+      function-bind: 1.1.2
+
+  is-binary-path@2.1.0:
+    dependencies:
+      binary-extensions: 2.3.0
+
+  is-core-module@2.16.1:
+    dependencies:
+      hasown: 2.0.2
+
+  is-extglob@2.1.1: {}
+
+  is-glob@4.0.3:
+    dependencies:
+      is-extglob: 2.1.1
+
+  is-number@7.0.0: {}
+
+  jiti@1.21.7: {}
+
+  lilconfig@3.1.3: {}
+
+  lines-and-columns@1.2.4: {}
+
+  merge2@1.4.1: {}
+
+  micromatch@4.0.8:
+    dependencies:
+      braces: 3.0.3
+      picomatch: 2.3.1
+
+  mz@2.7.0:
+    dependencies:
+      any-promise: 1.3.0
+      object-assign: 4.1.1
+      thenify-all: 1.6.0
+
+  nanoid@3.3.11: {}
+
+  node-releases@2.0.27: {}
+
+  normalize-path@3.0.0: {}
+
+  object-assign@4.1.1: {}
+
+  object-hash@3.0.0: {}
+
+  path-parse@1.0.7: {}
+
+  picocolors@1.1.1: {}
+
+  picomatch@2.3.1: {}
+
+  picomatch@4.0.3: {}
+
+  pify@2.3.0: {}
+
+  pirates@4.0.7: {}
+
+  postcss-import@15.1.0(postcss@8.5.6):
+    dependencies:
+      postcss: 8.5.6
+      postcss-value-parser: 4.2.0
+      read-cache: 1.0.0
+      resolve: 1.22.11
+
+  postcss-js@4.1.0(postcss@8.5.6):
+    dependencies:
+      camelcase-css: 2.0.1
+      postcss: 8.5.6
+
+  postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6):
+    dependencies:
+      lilconfig: 3.1.3
+    optionalDependencies:
+      jiti: 1.21.7
+      postcss: 8.5.6
+
+  postcss-nested@6.2.0(postcss@8.5.6):
+    dependencies:
+      postcss: 8.5.6
+      postcss-selector-parser: 6.1.2
+
+  postcss-selector-parser@6.1.2:
+    dependencies:
+      cssesc: 3.0.0
+      util-deprecate: 1.0.2
+
+  postcss-value-parser@4.2.0: {}
+
+  postcss@8.5.6:
+    dependencies:
+      nanoid: 3.3.11
+      picocolors: 1.1.1
+      source-map-js: 1.2.1
+
+  queue-microtask@1.2.3: {}
+
+  read-cache@1.0.0:
+    dependencies:
+      pify: 2.3.0
+
+  readdirp@3.6.0:
+    dependencies:
+      picomatch: 2.3.1
+
+  resolve@1.22.11:
+    dependencies:
+      is-core-module: 2.16.1
+      path-parse: 1.0.7
+      supports-preserve-symlinks-flag: 1.0.0
+
+  reusify@1.1.0: {}
+
+  rollup@4.55.1:
+    dependencies:
+      '@types/estree': 1.0.8
+    optionalDependencies:
+      '@rollup/rollup-android-arm-eabi': 4.55.1
+      '@rollup/rollup-android-arm64': 4.55.1
+      '@rollup/rollup-darwin-arm64': 4.55.1
+      '@rollup/rollup-darwin-x64': 4.55.1
+      '@rollup/rollup-freebsd-arm64': 4.55.1
+      '@rollup/rollup-freebsd-x64': 4.55.1
+      '@rollup/rollup-linux-arm-gnueabihf': 4.55.1
+      '@rollup/rollup-linux-arm-musleabihf': 4.55.1
+      '@rollup/rollup-linux-arm64-gnu': 4.55.1
+      '@rollup/rollup-linux-arm64-musl': 4.55.1
+      '@rollup/rollup-linux-loong64-gnu': 4.55.1
+      '@rollup/rollup-linux-loong64-musl': 4.55.1
+      '@rollup/rollup-linux-ppc64-gnu': 4.55.1
+      '@rollup/rollup-linux-ppc64-musl': 4.55.1
+      '@rollup/rollup-linux-riscv64-gnu': 4.55.1
+      '@rollup/rollup-linux-riscv64-musl': 4.55.1
+      '@rollup/rollup-linux-s390x-gnu': 4.55.1
+      '@rollup/rollup-linux-x64-gnu': 4.55.1
+      '@rollup/rollup-linux-x64-musl': 4.55.1
+      '@rollup/rollup-openbsd-x64': 4.55.1
+      '@rollup/rollup-openharmony-arm64': 4.55.1
+      '@rollup/rollup-win32-arm64-msvc': 4.55.1
+      '@rollup/rollup-win32-ia32-msvc': 4.55.1
+      '@rollup/rollup-win32-x64-gnu': 4.55.1
+      '@rollup/rollup-win32-x64-msvc': 4.55.1
+      fsevents: 2.3.3
+
+  run-parallel@1.2.0:
+    dependencies:
+      queue-microtask: 1.2.3
+
+  source-map-js@1.2.1: {}
+
+  sucrase@3.35.1:
+    dependencies:
+      '@jridgewell/gen-mapping': 0.3.13
+      commander: 4.1.1
+      lines-and-columns: 1.2.4
+      mz: 2.7.0
+      pirates: 4.0.7
+      tinyglobby: 0.2.15
+      ts-interface-checker: 0.1.13
+
+  supports-preserve-symlinks-flag@1.0.0: {}
+
+  tailwindcss@3.4.19:
+    dependencies:
+      '@alloc/quick-lru': 5.2.0
+      arg: 5.0.2
+      chokidar: 3.6.0
+      didyoumean: 1.2.2
+      dlv: 1.1.3
+      fast-glob: 3.3.3
+      glob-parent: 6.0.2
+      is-glob: 4.0.3
+      jiti: 1.21.7
+      lilconfig: 3.1.3
+      micromatch: 4.0.8
+      normalize-path: 3.0.0
+      object-hash: 3.0.0
+      picocolors: 1.1.1
+      postcss: 8.5.6
+      postcss-import: 15.1.0(postcss@8.5.6)
+      postcss-js: 4.1.0(postcss@8.5.6)
+      postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)
+      postcss-nested: 6.2.0(postcss@8.5.6)
+      postcss-selector-parser: 6.1.2
+      resolve: 1.22.11
+      sucrase: 3.35.1
+    transitivePeerDependencies:
+      - tsx
+      - yaml
+
+  thenify-all@1.6.0:
+    dependencies:
+      thenify: 3.3.1
+
+  thenify@3.3.1:
+    dependencies:
+      any-promise: 1.3.0
+
+  tinyglobby@0.2.15:
+    dependencies:
+      fdir: 6.5.0(picomatch@4.0.3)
+      picomatch: 4.0.3
+
+  to-regex-range@5.0.1:
+    dependencies:
+      is-number: 7.0.0
+
+  ts-interface-checker@0.1.13: {}
+
+  update-browserslist-db@1.2.3(browserslist@4.28.1):
+    dependencies:
+      browserslist: 4.28.1
+      escalade: 3.2.0
+      picocolors: 1.1.1
+
+  util-deprecate@1.0.2: {}
+
+  vite@5.4.21:
+    dependencies:
+      esbuild: 0.21.5
+      postcss: 8.5.6
+      rollup: 4.55.1
+    optionalDependencies:
+      fsevents: 2.3.3

+ 6 - 0
Prototype/admin-template/postcss.config.js

@@ -0,0 +1,6 @@
+export default {
+  plugins: {
+    tailwindcss: {},
+    autoprefixer: {}
+  }
+}

+ 70 - 0
Prototype/admin-template/src/js/main.js

@@ -0,0 +1,70 @@
+// TG Admin Template - Main JavaScript
+
+document.addEventListener('DOMContentLoaded', () => {
+  initSidebar()
+  initDropdowns()
+})
+
+/**
+ * Sidebar Toggle (Mobile)
+ */
+function initSidebar() {
+  const sidebarToggle = document.getElementById('sidebar-toggle')
+  const sidebarClose = document.getElementById('sidebar-close')
+  const sidebar = document.getElementById('sidebar')
+  const overlay = document.getElementById('sidebar-overlay')
+
+  if (sidebarToggle && sidebar) {
+    sidebarToggle.addEventListener('click', () => {
+      sidebar.classList.remove('-translate-x-full')
+      overlay?.classList.remove('hidden')
+    })
+  }
+
+  if (sidebarClose && sidebar) {
+    sidebarClose.addEventListener('click', () => {
+      sidebar.classList.add('-translate-x-full')
+      overlay?.classList.add('hidden')
+    })
+  }
+
+  if (overlay && sidebar) {
+    overlay.addEventListener('click', () => {
+      sidebar.classList.add('-translate-x-full')
+      overlay.classList.add('hidden')
+    })
+  }
+}
+
+/**
+ * Dropdown Menus
+ */
+function initDropdowns() {
+  const dropdownToggles = document.querySelectorAll('[data-dropdown-toggle]')
+
+  dropdownToggles.forEach((toggle) => {
+    const targetId = toggle.getAttribute('data-dropdown-toggle')
+    const dropdown = document.getElementById(targetId)
+
+    if (dropdown) {
+      toggle.addEventListener('click', (e) => {
+        e.stopPropagation()
+        dropdown.classList.toggle('hidden')
+      })
+    }
+  })
+
+  // Close dropdowns when clicking outside
+  document.addEventListener('click', () => {
+    document.querySelectorAll('[data-dropdown]').forEach((dropdown) => {
+      dropdown.classList.add('hidden')
+    })
+  })
+}
+
+/**
+ * Delete Confirmation
+ */
+window.confirmDelete = function (message = '确定要删除吗?') {
+  return confirm(message)
+}

+ 110 - 0
Prototype/admin-template/src/styles/main.css

@@ -0,0 +1,110 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+/* Google Fonts - Inter */
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
+
+@layer base {
+  html {
+    font-family: 'Inter', system-ui, -apple-system, sans-serif;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+  }
+
+  body {
+    @apply bg-white text-gray-900;
+  }
+
+  /* Remove default focus outline, add custom */
+  input:focus,
+  textarea:focus,
+  select:focus {
+    @apply outline-none;
+  }
+}
+
+@layer components {
+  /* Primary Button - Black */
+  .btn-primary {
+    @apply w-full py-3.5 bg-black hover:bg-gray-900 active:bg-gray-800
+           text-white font-semibold rounded-none transition-colors
+           cursor-pointer text-base;
+  }
+
+  /* Secondary Button - Outline */
+  .btn-secondary {
+    @apply w-full py-3 border border-gray-200 hover:border-black
+           hover:bg-gray-50 text-black font-medium rounded-none
+           transition-colors cursor-pointer;
+  }
+
+  /* Input - Underline style */
+  .input-underline {
+    @apply w-full px-0 py-3 bg-transparent border-b border-gray-200
+           text-black text-base placeholder-gray-300
+           focus:outline-none focus:border-black transition-colors;
+  }
+
+  /* Input - Boxed style */
+  .input-box {
+    @apply w-full px-4 py-3 bg-gray-50 border border-gray-200
+           text-black text-base placeholder-gray-400
+           focus:outline-none focus:border-black focus:bg-white
+           transition-colors;
+  }
+
+  /* Card */
+  .card {
+    @apply bg-white border border-gray-200 p-6;
+  }
+
+  /* Sidebar Navigation Item */
+  .nav-item {
+    @apply flex items-center gap-3 px-4 py-3 text-gray-400
+           hover:text-white hover:bg-white/5 transition-colors cursor-pointer;
+  }
+
+  .nav-item.active {
+    @apply text-white bg-white/10;
+  }
+
+  /* Table styles */
+  .table-header {
+    @apply text-left text-xs font-semibold text-gray-500 uppercase tracking-wider;
+  }
+
+  .table-cell {
+    @apply py-4 text-sm text-gray-900;
+  }
+
+  .table-row {
+    @apply border-b border-gray-100 hover:bg-gray-50 transition-colors;
+  }
+
+  /* Badge */
+  .badge {
+    @apply inline-flex items-center px-2.5 py-0.5 text-xs font-medium;
+  }
+
+  .badge-success {
+    @apply bg-green-100 text-green-800;
+  }
+
+  .badge-warning {
+    @apply bg-yellow-100 text-yellow-800;
+  }
+
+  .badge-danger {
+    @apply bg-red-100 text-red-800;
+  }
+}
+
+@layer utilities {
+  /* Smooth transitions for all interactive elements */
+  .transition-default {
+    transition-property: color, background-color, border-color, opacity;
+    transition-duration: 150ms;
+    transition-timing-function: ease-in-out;
+  }
+}

+ 21 - 0
Prototype/admin-template/tailwind.config.js

@@ -0,0 +1,21 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+  content: ['./*.html', './pages/**/*.html', './src/**/*.{js,ts}'],
+  theme: {
+    extend: {
+      fontFamily: {
+        sans: ['Inter', 'system-ui', '-apple-system', 'BlinkMacSystemFont', 'sans-serif']
+      },
+      colors: {
+        brand: {
+          DEFAULT: '#000000',
+          hover: '#1a1a1a'
+        }
+      },
+      transitionProperty: {
+        colors: 'color, background-color, border-color, text-decoration-color, fill, stroke'
+      }
+    }
+  },
+  plugins: []
+}

+ 19 - 0
Prototype/admin-template/vite.config.js

@@ -0,0 +1,19 @@
+import { resolve } from 'path'
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+  root: '.',
+  build: {
+    rollupOptions: {
+      input: {
+        main: resolve(__dirname, 'index.html'),
+        dashboard: resolve(__dirname, 'pages/dashboard.html'),
+        users: resolve(__dirname, 'pages/users.html'),
+        settings: resolve(__dirname, 'pages/settings.html'),
+        notfound: resolve(__dirname, 'pages/404.html'),
+        login: resolve(__dirname, 'pages/auth/login.html'),
+        register: resolve(__dirname, 'pages/auth/register.html')
+      }
+    }
+  }
+})

+ 175 - 0
Prototype/login/login-responsive.html

@@ -0,0 +1,175 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>登录 - TG Live Game</title>
+    <script src="https://cdn.tailwindcss.com"></script>
+    <script>
+        tailwind.config = {
+            theme: {
+                extend: {
+                    fontFamily: {
+                        sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
+                    },
+                    colors: {
+                        brand: {
+                            DEFAULT: '#000000',
+                            hover: '#1a1a1a',
+                        }
+                    }
+                }
+            }
+        }
+    </script>
+    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
+    <style>
+        /* Custom focus styles */
+        input:focus {
+            box-shadow: none;
+        }
+        /* Smooth transitions */
+        * {
+            transition-property: color, background-color, border-color, opacity;
+            transition-duration: 150ms;
+            transition-timing-function: ease-in-out;
+        }
+    </style>
+</head>
+<body class="min-h-screen bg-white font-sans antialiased">
+    <!-- Mobile: Single column | Desktop: Split screen -->
+    <div class="min-h-screen flex flex-col lg:flex-row">
+
+        <!-- Left Panel - Hidden on mobile, visible on lg+ -->
+        <div class="hidden lg:flex lg:w-1/2 xl:w-2/5 bg-gradient-to-br from-gray-900 via-gray-800 to-black p-8 xl:p-12 flex-col justify-between">
+            <!-- Logo -->
+            <div class="flex items-center gap-3">
+                <div class="w-10 h-10 bg-white rounded-lg flex items-center justify-center">
+                    <svg class="w-5 h-5 text-black" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
+                    </svg>
+                </div>
+                <span class="text-white font-semibold text-lg">TG Live Game</span>
+            </div>
+
+            <!-- Center content -->
+            <div class="flex-1 flex flex-col justify-center">
+                <h2 class="text-3xl xl:text-4xl font-bold text-white mb-4 leading-tight">
+                    开启你的<br>直播之旅
+                </h2>
+                <p class="text-gray-400 text-base xl:text-lg max-w-sm">
+                    加入数万创作者,打造属于你的直播舞台
+                </p>
+
+                <!-- Stats -->
+                <div class="flex gap-8 mt-10">
+                    <div>
+                        <div class="text-2xl xl:text-3xl font-bold text-white">50K+</div>
+                        <div class="text-gray-500 text-sm">创作者</div>
+                    </div>
+                    <div>
+                        <div class="text-2xl xl:text-3xl font-bold text-white">99.9%</div>
+                        <div class="text-gray-500 text-sm">稳定性</div>
+                    </div>
+                    <div>
+                        <div class="text-2xl xl:text-3xl font-bold text-white">24/7</div>
+                        <div class="text-gray-500 text-sm">技术支持</div>
+                    </div>
+                </div>
+            </div>
+
+            <!-- Footer -->
+            <div class="text-gray-600 text-sm">
+                &copy; 2025 TG Live Game. All rights reserved.
+            </div>
+        </div>
+
+        <!-- Right Panel - Login Form -->
+        <div class="flex-1 flex items-center justify-center p-6 sm:p-8 md:p-12 lg:p-16">
+            <div class="w-full max-w-sm sm:max-w-md">
+
+                <!-- Mobile Logo - Only visible on mobile -->
+                <div class="flex items-center gap-2 mb-8 sm:mb-12 lg:hidden">
+                    <div class="w-8 h-8 sm:w-10 sm:h-10 bg-black rounded-lg flex items-center justify-center">
+                        <svg class="w-4 h-4 sm:w-5 sm:h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
+                        </svg>
+                    </div>
+                    <span class="font-semibold text-black text-base sm:text-lg">TG Live</span>
+                </div>
+
+                <!-- Title -->
+                <div class="mb-8 sm:mb-10 lg:mb-12">
+                    <h1 class="text-3xl sm:text-4xl lg:text-5xl font-extrabold text-black tracking-tight leading-none mb-2 sm:mb-3">
+                        登录
+                    </h1>
+                    <p class="text-gray-400 text-base sm:text-lg">欢迎回来</p>
+                </div>
+
+                <!-- Form -->
+                <form class="space-y-6 sm:space-y-8">
+                    <div>
+                        <label class="block text-sm font-medium text-gray-700 mb-2 sm:hidden">用户名</label>
+                        <input type="text" placeholder="用户名"
+                            class="w-full px-0 py-3 sm:py-4 bg-transparent border-b border-gray-200 text-black text-base sm:text-lg placeholder-gray-300 focus:outline-none focus:border-black">
+                    </div>
+
+                    <div>
+                        <label class="block text-sm font-medium text-gray-700 mb-2 sm:hidden">密码</label>
+                        <input type="password" placeholder="密码"
+                            class="w-full px-0 py-3 sm:py-4 bg-transparent border-b border-gray-200 text-black text-base sm:text-lg placeholder-gray-300 focus:outline-none focus:border-black">
+                    </div>
+
+                    <div class="flex items-center justify-between text-sm sm:text-base">
+                        <label class="flex items-center text-gray-500 cursor-pointer group">
+                            <input type="checkbox" class="w-4 h-4 sm:w-5 sm:h-5 rounded border-gray-300 text-black focus:ring-black focus:ring-offset-0 mr-2 sm:mr-3 cursor-pointer">
+                            <span class="group-hover:text-gray-700">记住我</span>
+                        </label>
+                        <a href="#" class="text-black font-medium hover:underline underline-offset-4">忘记密码?</a>
+                    </div>
+
+                    <button type="submit" class="w-full py-3.5 sm:py-4 bg-black hover:bg-gray-900 active:bg-gray-800 text-white font-semibold rounded-none transition-all cursor-pointer text-base sm:text-lg touch-manipulation">
+                        登录
+                    </button>
+                </form>
+
+                <!-- Divider -->
+                <div class="flex items-center my-8 sm:my-10">
+                    <div class="flex-1 h-px bg-gray-200"></div>
+                    <span class="px-4 sm:px-6 text-gray-300 text-xs sm:text-sm uppercase tracking-wider">或</span>
+                    <div class="flex-1 h-px bg-gray-200"></div>
+                </div>
+
+                <!-- Social Login -->
+                <div class="space-y-3">
+                    <button class="w-full py-3 sm:py-3.5 border border-gray-200 hover:border-black hover:bg-gray-50 text-black font-medium flex items-center justify-center gap-2 sm:gap-3 cursor-pointer touch-manipulation">
+                        <svg class="w-4 h-4 sm:w-5 sm:h-5" viewBox="0 0 24 24">
+                            <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
+                            <path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
+                            <path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
+                            <path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
+                        </svg>
+                        <span class="text-sm sm:text-base">使用 Google 继续</span>
+                    </button>
+                    <button class="w-full py-3 sm:py-3.5 border border-gray-200 hover:border-black hover:bg-gray-50 text-black font-medium flex items-center justify-center gap-2 sm:gap-3 cursor-pointer touch-manipulation">
+                        <svg class="w-4 h-4 sm:w-5 sm:h-5" viewBox="0 0 24 24" fill="currentColor">
+                            <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
+                        </svg>
+                        <span class="text-sm sm:text-base">使用 GitHub 继续</span>
+                    </button>
+                </div>
+
+                <!-- Footer -->
+                <p class="text-center text-gray-400 text-sm sm:text-base mt-8 sm:mt-12">
+                    还没有账户?<a href="#" class="text-black font-medium hover:underline underline-offset-4">注册</a>
+                </p>
+
+                <!-- Mobile Footer - Only visible on mobile -->
+                <p class="text-center text-gray-400 text-xs mt-8 lg:hidden">
+                    &copy; 2025 TG Live Game
+                </p>
+            </div>
+        </div>
+    </div>
+</body>
+</html>

+ 575 - 221
src/layout/index.vue

@@ -1,132 +1,123 @@
 <template>
-  <el-container class="app-wrapper">
-    <el-aside
-      :width="sidebarOpened ? '210px' : '64px'"
-      :class="sidebarOpened ? 'sidebar-container' : 'sidebar-container collapsed'"
+  <div class="layout">
+    <!-- Sidebar Overlay (Mobile) -->
+    <div v-if="sidebarOpened && isMobile" class="layout__overlay" @click="closeSidebar"></div>
+
+    <!-- Sidebar -->
+    <aside
+      class="layout__sidebar"
+      :class="{
+        'layout__sidebar--open': sidebarOpened,
+        'layout__sidebar--collapsed': !sidebarOpened && !isMobile
+      }"
     >
-      <div class="logo">
-        <img src="@/assets/logo.svg" alt="logo" />
-        <h1 v-show="sidebarOpened">摄像头管理</h1>
+      <!-- Logo -->
+      <div class="layout__logo">
+        <div class="layout__logo-icon">
+          <svg class="layout__icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+            <path
+              stroke-linecap="round"
+              stroke-linejoin="round"
+              stroke-width="2"
+              d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
+            />
+          </svg>
+        </div>
+        <span v-show="sidebarOpened || isMobile" class="layout__logo-text">{{ t('摄像头管理系统') }}</span>
+        <!-- Close button (Mobile) -->
+        <button v-if="isMobile" class="layout__close" @click="closeSidebar">
+          <svg class="layout__icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
+          </svg>
+        </button>
+      </div>
+
+      <!-- Navigation -->
+      <nav class="layout__nav">
+        <router-link
+          v-for="item in menuItems"
+          :key="item.path"
+          :to="item.path"
+          class="layout__nav-item"
+          :class="{ 'layout__nav-item--active': isActive(item.path) }"
+          @click="isMobile && closeSidebar()"
+        >
+          <component :is="item.icon" class="layout__nav-icon" />
+          <span v-show="sidebarOpened || isMobile">{{ t(item.title) }}</span>
+        </router-link>
+      </nav>
+
+      <!-- Footer -->
+      <div class="layout__sidebar-footer">
+        <button class="layout__logout" @click="handleLogout">
+          <svg class="layout__nav-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+            <path
+              stroke-linecap="round"
+              stroke-linejoin="round"
+              stroke-width="2"
+              d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
+            />
+          </svg>
+          <span v-show="sidebarOpened || isMobile">{{ t('退出登录') }}</span>
+        </button>
       </div>
-      <el-menu
-        :default-active="activeMenu"
-        :collapse="!sidebarOpened"
-        :collapse-transition="false"
-        :background-color="isDark ? '#18181b' : '#1e293b'"
-        :text-color="isDark ? '#a1a1aa' : '#94a3b8'"
-        active-text-color="var(--color-primary)"
-        router
-      >
-        <el-menu-item index="/machine">
-          <el-icon><Monitor /></el-icon>
-          <template #title>{{ t('机器管理') }}</template>
-        </el-menu-item>
-
-        <el-menu-item index="/camera">
-          <el-icon><VideoCamera /></el-icon>
-          <template #title>{{ t('摄像头管理') }}</template>
-        </el-menu-item>
-
-        <el-menu-item index="/user">
-          <el-icon><UserFilled /></el-icon>
-          <template #title>{{ t('用户管理') }}</template>
-        </el-menu-item>
-
-        <el-menu-item index="/cc">
-          <el-icon><VideoCamera /></el-icon>
-          <template #title>{{ t('Cloudflare Stream') }}</template>
-        </el-menu-item>
-
-        <el-menu-item index="/webrtc">
-          <el-icon><Connection /></el-icon>
-          <template #title>{{ t('WebRTC 流') }}</template>
-        </el-menu-item>
-
-        <el-sub-menu index="/demo">
-          <template #title>
-            <el-icon><VideoPlay /></el-icon>
-            <span>{{ t('视频测试') }}</span>
-          </template>
-          <el-menu-item index="/demo/directurl">
-            <el-icon><Link /></el-icon>
-            <template #title>{{ t('直接 URL') }}</template>
-          </el-menu-item>
-          <el-menu-item index="/demo/rtsp">
-            <el-icon><Connection /></el-icon>
-            <template #title>{{ t('RTSP 流') }}</template>
-          </el-menu-item>
-          <el-menu-item index="/demo/samples">
-            <el-icon><Film /></el-icon>
-            <template #title>{{ t('测试视频') }}</template>
-          </el-menu-item>
-        </el-sub-menu>
-
-        <el-sub-menu index="/stream">
-          <template #title>
-            <el-icon><Film /></el-icon>
-            <span>{{ t('Cloudflare Stream') }}</span>
-          </template>
-          <el-menu-item index="/stream/videos">
-            <el-icon><Film /></el-icon>
-            <template #title>{{ t('视频管理') }}</template>
-          </el-menu-item>
-          <el-menu-item index="/stream/live">
-            <el-icon><VideoCameraFilled /></el-icon>
-            <template #title>{{ t('直播管理') }}</template>
-          </el-menu-item>
-          <el-menu-item index="/stream/config">
-            <el-icon><Setting /></el-icon>
-            <template #title>{{ t('Stream 配置') }}</template>
-          </el-menu-item>
-          <el-menu-item index="/streamtest">
-            <el-icon><Monitor /></el-icon>
-            <template #title>{{ t('快速测试') }}</template>
-          </el-menu-item>
-        </el-sub-menu>
-      </el-menu>
-    </el-aside>
-
-    <el-container class="main-container">
-      <el-header class="app-header">
-        <div class="header-left">
-          <el-icon class="collapse-btn" @click="toggleSidebar">
-            <Fold v-if="sidebarOpened" />
-            <Expand v-else />
-          </el-icon>
-          <el-breadcrumb separator="/">
-            <el-breadcrumb-item v-for="item in breadcrumbs" :key="item.path">
-              {{ t(item.meta?.title as string) }}
-            </el-breadcrumb-item>
-          </el-breadcrumb>
+    </aside>
+
+    <!-- Main Content -->
+    <div class="layout__main">
+      <!-- Header -->
+      <header class="layout__header">
+        <!-- Mobile menu button -->
+        <button class="layout__menu-btn" @click="toggleSidebar">
+          <svg class="layout__icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
+          </svg>
+        </button>
+
+        <!-- Page Title / Breadcrumb -->
+        <div class="layout__breadcrumb">
+          <span v-for="(item, index) in breadcrumbs" :key="item.path" class="layout__breadcrumb-item">
+            <span v-if="index > 0" class="layout__breadcrumb-separator">/</span>
+            {{ t(item.meta?.title as string) }}
+          </span>
         </div>
-        <div class="header-right">
-          <ThemeSwitch @open-settings="themeSettingsVisible = true" />
+
+        <!-- Header Right -->
+        <div class="layout__header-right">
           <LangDropdown />
 
-          <el-dropdown @command="handleCommand">
-            <span class="user-info">
-              <el-avatar :size="32" :src="'https://cube.elemecdn.com/0/88/03b0d39583f4b3a790e9d12d41f23e23jps.jpg'" />
-              <span class="username">{{ userInfo?.username }}</span>
-              <el-icon><ArrowDown /></el-icon>
-            </span>
-            <template #dropdown>
-              <el-dropdown-menu>
-                <el-dropdown-item command="changePassword">{{ t('修改密码') }}</el-dropdown-item>
-                <el-dropdown-item command="logout">{{ t('退出登录') }}</el-dropdown-item>
-              </el-dropdown-menu>
-            </template>
-          </el-dropdown>
+          <!-- User Menu -->
+          <div class="layout__user" @click="toggleUserMenu">
+            <div class="layout__avatar">
+              <span>{{ userInitial }}</span>
+            </div>
+            <span class="layout__username">{{ userInfo?.username }}</span>
+            <svg class="layout__icon layout__icon--sm" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
+            </svg>
+
+            <!-- User Dropdown -->
+            <div v-show="userMenuOpen" class="layout__dropdown">
+              <button class="layout__dropdown-item" @click="openChangePassword">
+                {{ t('修改密码') }}
+              </button>
+              <button class="layout__dropdown-item" @click="handleLogout">
+                {{ t('退出登录') }}
+              </button>
+            </div>
+          </div>
         </div>
-      </el-header>
+      </header>
 
-      <el-main class="app-main">
+      <!-- Page Content -->
+      <main class="layout__content">
         <router-view v-slot="{ Component }">
           <transition name="fade" mode="out-in">
             <component :is="Component" />
           </transition>
         </router-view>
-      </el-main>
-    </el-container>
+      </main>
+    </div>
 
     <!-- 修改密码弹窗 -->
     <el-dialog v-model="passwordDialogVisible" :title="t('修改密码')" width="400px" :close-on-click-modal="false">
@@ -151,62 +142,181 @@
         <el-button type="primary" :loading="passwordLoading" @click="handleChangePassword">{{ t('确定') }}</el-button>
       </template>
     </el-dialog>
-
-    <!-- 主题设置抽屉 -->
-    <ThemeSettings v-model="themeSettingsVisible" />
-  </el-container>
+  </div>
 </template>
 
 <script setup lang="ts">
-import { computed, onMounted, ref, reactive } from 'vue'
+import { computed, onMounted, onUnmounted, ref, reactive, h, type Component } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
-import {
-  VideoCamera,
-  Fold,
-  Expand,
-  ArrowDown,
-  Monitor,
-  Film,
-  VideoCameraFilled,
-  Setting,
-  UserFilled,
-  VideoPlay,
-  Link,
-  Connection
-} from '@element-plus/icons-vue'
 import LangDropdown from '@/components/LangDropdown.vue'
-import ThemeSwitch from '@/components/ThemeSwitch.vue'
-import ThemeSettings from '@/components/ThemeSettings.vue'
 import { useAppStore } from '@/store/app'
 import { useUserStore } from '@/store/user'
-import { useThemeStore } from '@/store/theme'
 import { changePassword } from '@/api/login'
 import { useI18n } from 'vue-i18n'
+
 const route = useRoute()
 const router = useRouter()
 const appStore = useAppStore()
 const userStore = useUserStore()
-const themeStore = useThemeStore()
 const { t } = useI18n()
+
 const sidebarOpened = computed(() => appStore.sidebarOpened)
-const themeSettingsVisible = ref(false)
-const isDark = computed(() => themeStore.isDark)
 const userInfo = computed(() => userStore.userInfo)
+const userMenuOpen = ref(false)
+const isMobile = ref(false)
+
+// Icon components
+const DashboardIcon = {
+  render: () =>
+    h('svg', { class: 'layout__nav-icon', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' }, [
+      h('path', {
+        'stroke-linecap': 'round',
+        'stroke-linejoin': 'round',
+        'stroke-width': '2',
+        d: 'M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6'
+      })
+    ])
+}
+
+const MachineIcon = {
+  render: () =>
+    h('svg', { class: 'layout__nav-icon', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' }, [
+      h('path', {
+        'stroke-linecap': 'round',
+        'stroke-linejoin': 'round',
+        'stroke-width': '2',
+        d: 'M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z'
+      })
+    ])
+}
+
+const CameraIcon = {
+  render: () =>
+    h('svg', { class: 'layout__nav-icon', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' }, [
+      h('path', {
+        'stroke-linecap': 'round',
+        'stroke-linejoin': 'round',
+        'stroke-width': '2',
+        d: 'M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z'
+      })
+    ])
+}
+
+const UserIcon = {
+  render: () =>
+    h('svg', { class: 'layout__nav-icon', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' }, [
+      h('path', {
+        'stroke-linecap': 'round',
+        'stroke-linejoin': 'round',
+        'stroke-width': '2',
+        d: 'M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z'
+      })
+    ])
+}
+
+const StatsIcon = {
+  render: () =>
+    h('svg', { class: 'layout__nav-icon', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' }, [
+      h('path', {
+        'stroke-linecap': 'round',
+        'stroke-linejoin': 'round',
+        'stroke-width': '2',
+        d: 'M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z'
+      })
+    ])
+}
 
-const activeMenu = computed(() => {
-  const { path } = route
-  return path
+const AuditIcon = {
+  render: () =>
+    h('svg', { class: 'layout__nav-icon', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' }, [
+      h('path', {
+        'stroke-linecap': 'round',
+        'stroke-linejoin': 'round',
+        'stroke-width': '2',
+        d: 'M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z'
+      })
+    ])
+}
+
+const StreamIcon = {
+  render: () =>
+    h('svg', { class: 'layout__nav-icon', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' }, [
+      h('path', {
+        'stroke-linecap': 'round',
+        'stroke-linejoin': 'round',
+        'stroke-width': '2',
+        d: 'M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z'
+      }),
+      h('path', {
+        'stroke-linecap': 'round',
+        'stroke-linejoin': 'round',
+        'stroke-width': '2',
+        d: 'M21 12a9 9 0 11-18 0 9 9 0 0118 0z'
+      })
+    ])
+}
+
+interface MenuItem {
+  path: string
+  title: string
+  icon: Component
+}
+
+const menuItems: MenuItem[] = [
+  { path: '/', title: '仪表盘', icon: DashboardIcon },
+  { path: '/machine', title: '机器管理', icon: MachineIcon },
+  { path: '/camera', title: '摄像头管理', icon: CameraIcon },
+  { path: '/user', title: '用户管理', icon: UserIcon },
+  { path: '/stats', title: '观看统计', icon: StatsIcon },
+  { path: '/audit', title: '审计日志', icon: AuditIcon },
+  { path: '/stream-test', title: 'Stream 测试', icon: StreamIcon }
+]
+
+const userInitial = computed(() => {
+  const name = userInfo.value?.username || 'A'
+  return name.charAt(0).toUpperCase()
 })
 
 const breadcrumbs = computed(() => {
   return route.matched.filter((item) => item.meta && item.meta.title && !item.meta.hidden)
 })
 
+function isActive(path: string) {
+  if (path === '/') {
+    return route.path === '/' || route.path === '/dashboard'
+  }
+  return route.path.startsWith(path)
+}
+
 function toggleSidebar() {
   appStore.toggleSidebar()
 }
 
+function closeSidebar() {
+  if (isMobile.value) {
+    appStore.closeSidebar()
+  }
+}
+
+function toggleUserMenu() {
+  userMenuOpen.value = !userMenuOpen.value
+}
+
+function closeUserMenu(e: MouseEvent) {
+  const target = e.target as HTMLElement
+  if (!target.closest('.layout__user')) {
+    userMenuOpen.value = false
+  }
+}
+
+function checkMobile() {
+  isMobile.value = window.innerWidth < 1024
+  if (isMobile.value) {
+    appStore.closeSidebar()
+  }
+}
+
 // 修改密码相关
 const passwordDialogVisible = ref(false)
 const passwordLoading = ref(false)
@@ -244,6 +354,12 @@ function resetPasswordForm() {
   passwordFormRef.value?.clearValidate()
 }
 
+function openChangePassword() {
+  userMenuOpen.value = false
+  resetPasswordForm()
+  passwordDialogVisible.value = true
+}
+
 async function handleChangePassword() {
   if (!passwordFormRef.value) return
 
@@ -273,123 +389,361 @@ async function handleChangePassword() {
   })
 }
 
-async function handleCommand(command: string) {
-  if (command === 'changePassword') {
-    resetPasswordForm()
-    passwordDialogVisible.value = true
-  } else if (command === 'logout') {
-    await userStore.logoutAction()
-    router.push('/login')
-  }
+async function handleLogout() {
+  userMenuOpen.value = false
+  await userStore.logoutAction()
+  router.push('/login')
 }
 
 onMounted(() => {
   userStore.getUserInfo()
+  checkMobile()
+  window.addEventListener('resize', checkMobile)
+  document.addEventListener('click', closeUserMenu)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', checkMobile)
+  document.removeEventListener('click', closeUserMenu)
 })
 </script>
 
 <style lang="scss" scoped>
-.app-wrapper {
-  height: 100%;
-}
+.layout {
+  display: flex;
+  min-height: 100vh;
+  font-family: 'Inter', system-ui, -apple-system, sans-serif;
+
+  // Overlay
+  &__overlay {
+    position: fixed;
+    inset: 0;
+    background: rgba(0, 0, 0, 0.5);
+    z-index: 40;
+
+    @media (min-width: 1024px) {
+      display: none;
+    }
+  }
 
-.sidebar-container {
-  background-color: var(--bg-sidebar);
-  transition: width var(--transition-base) var(--transition-timing),
-    background-color var(--transition-base) var(--transition-timing);
-  overflow: auto;
+  // Sidebar
+  &__sidebar {
+    position: fixed;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    width: 16rem;
+    background: linear-gradient(to bottom, #111827, #1f2937, #000000);
+    display: flex;
+    flex-direction: column;
+    z-index: 50;
+    transform: translateX(-100%);
+    transition: transform 200ms ease-in-out, width 200ms ease-in-out;
+
+    @media (min-width: 1024px) {
+      position: static;
+      transform: translateX(0);
+    }
 
-  .logo {
-    height: var(--header-height);
+    &--open {
+      transform: translateX(0);
+    }
+
+    &--collapsed {
+      width: 4rem;
+
+      .layout__logo-text,
+      .layout__nav-item span,
+      .layout__logout span {
+        display: none;
+      }
+
+      .layout__logo {
+        justify-content: center;
+        padding: 0;
+      }
+
+      .layout__nav-item,
+      .layout__logout {
+        justify-content: center;
+        padding: 0.75rem;
+      }
+    }
+  }
+
+  // Logo
+  &__logo {
     display: flex;
     align-items: center;
-    padding: 0 15px;
-    background-color: var(--bg-sidebar-logo);
-    transition: background-color var(--transition-base) var(--transition-timing);
+    justify-content: space-between;
+    height: 4rem;
+    padding: 0 1.5rem;
+    border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+  }
 
-    img {
-      width: 32px;
-      height: 32px;
+  &__logo-icon {
+    width: 2rem;
+    height: 2rem;
+    background: #ffffff;
+    border-radius: 0.5rem;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-shrink: 0;
+
+    .layout__icon {
+      width: 1rem;
+      height: 1rem;
+      color: #000000;
     }
+  }
 
-    h1 {
-      margin-left: 12px;
-      font-size: var(--font-size-lg);
-      font-weight: var(--font-weight-semibold);
-      color: var(--text-inverse);
-      white-space: nowrap;
+  &__logo-text {
+    flex: 1;
+    margin-left: 0.75rem;
+    color: #ffffff;
+    font-weight: 600;
+    font-size: 0.875rem;
+    white-space: nowrap;
+  }
+
+  &__close {
+    background: none;
+    border: none;
+    color: #9ca3af;
+    cursor: pointer;
+    padding: 0.25rem;
+    transition: color 150ms ease-in-out;
+
+    &:hover {
+      color: #ffffff;
+    }
+
+    @media (min-width: 1024px) {
+      display: none;
     }
   }
 
-  .el-menu {
-    border-right: none;
+  // Navigation
+  &__nav {
+    flex: 1;
+    padding: 1.5rem 1rem;
+    overflow-y: auto;
+    display: flex;
+    flex-direction: column;
+    gap: 0.25rem;
   }
-}
 
-.sidebar-container.collapsed {
-  overflow: hidden;
-}
+  &__nav-item {
+    display: flex;
+    align-items: center;
+    gap: 0.75rem;
+    padding: 0.75rem 1rem;
+    color: #9ca3af;
+    text-decoration: none;
+    font-size: 0.875rem;
+    border-radius: 0.25rem;
+    transition: all 150ms ease-in-out;
+    cursor: pointer;
+
+    &:hover {
+      color: #ffffff;
+      background: rgba(255, 255, 255, 0.05);
+    }
 
-.main-container {
-  flex-direction: column;
-  min-height: 100%;
-  overflow: hidden;
-}
+    &--active {
+      color: #ffffff;
+      background: rgba(255, 255, 255, 0.1);
+    }
+  }
 
-.app-header {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  height: var(--header-height);
-  background-color: var(--bg-header);
-  box-shadow: var(--shadow-header);
-  transition: background-color var(--transition-base) var(--transition-timing),
-    box-shadow var(--transition-base) var(--transition-timing);
-
-  .header-left {
+  &__nav-icon {
+    width: 1.25rem;
+    height: 1.25rem;
+    flex-shrink: 0;
+  }
+
+  // Sidebar Footer
+  &__sidebar-footer {
+    padding: 1rem 1rem 1.5rem;
+    border-top: 1px solid rgba(255, 255, 255, 0.1);
+  }
+
+  &__logout {
     display: flex;
     align-items: center;
+    gap: 0.75rem;
+    width: 100%;
+    padding: 0.75rem 1rem;
+    color: #9ca3af;
+    background: none;
+    border: none;
+    font-size: 0.875rem;
+    cursor: pointer;
+    transition: color 150ms ease-in-out;
+
+    &:hover {
+      color: #ffffff;
+    }
+  }
 
-    .collapse-btn {
-      font-size: 20px;
-      cursor: pointer;
-      margin-right: 15px;
-      color: var(--text-secondary);
-      transition: color var(--transition-fast) var(--transition-timing);
+  // Main
+  &__main {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    min-width: 0;
+    background: #f9fafb;
+  }
 
-      &:hover {
-        color: var(--color-primary);
-      }
+  // Header
+  &__header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    height: 4rem;
+    padding: 0 1rem;
+    background: #ffffff;
+    border-bottom: 1px solid #e5e7eb;
+
+    @media (min-width: 1024px) {
+      padding: 0 2rem;
+    }
+  }
+
+  &__menu-btn {
+    padding: 0.5rem;
+    margin-left: -0.5rem;
+    background: none;
+    border: none;
+    color: #6b7280;
+    cursor: pointer;
+    transition: color 150ms ease-in-out;
+
+    &:hover {
+      color: #000000;
+    }
+  }
+
+  &__breadcrumb {
+    display: none;
+    font-size: 1rem;
+    font-weight: 600;
+    color: #000000;
+
+    @media (min-width: 1024px) {
+      display: block;
     }
   }
 
-  .header-right {
+  &__breadcrumb-item {
+    color: #000000;
+  }
+
+  &__breadcrumb-separator {
+    color: #9ca3af;
+    margin: 0 0.5rem;
+  }
+
+  &__header-right {
+    display: flex;
+    align-items: center;
+    gap: 1rem;
+  }
+
+  // User Menu
+  &__user {
+    position: relative;
     display: flex;
     align-items: center;
-    gap: 16px;
+    gap: 0.75rem;
+    cursor: pointer;
+  }
 
-    .user-info {
-      display: flex;
-      align-items: center;
-      cursor: pointer;
+  &__avatar {
+    width: 2rem;
+    height: 2rem;
+    background: #111827;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
 
-      .username {
-        margin: 0 8px;
-        color: var(--text-secondary);
-      }
+    span {
+      color: #ffffff;
+      font-size: 0.875rem;
+      font-weight: 500;
     }
   }
-}
 
-.app-main {
-  background-color: var(--bg-page);
-  overflow: auto;
-  transition: background-color var(--transition-base) var(--transition-timing);
+  &__username {
+    display: none;
+    font-size: 0.875rem;
+    font-weight: 500;
+    color: #374151;
+
+    @media (min-width: 640px) {
+      display: block;
+    }
+  }
+
+  &__dropdown {
+    position: absolute;
+    top: 100%;
+    right: 0;
+    margin-top: 0.5rem;
+    min-width: 12rem;
+    background: #ffffff;
+    border: 1px solid #e5e7eb;
+    box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+    z-index: 50;
+  }
+
+  &__dropdown-item {
+    display: block;
+    width: 100%;
+    padding: 0.5rem 1rem;
+    text-align: left;
+    font-size: 0.875rem;
+    color: #374151;
+    background: none;
+    border: none;
+    cursor: pointer;
+    transition: background-color 150ms ease-in-out;
+
+    &:hover {
+      background: #f9fafb;
+    }
+  }
+
+  // Content
+  &__content {
+    flex: 1;
+    padding: 1rem;
+    overflow: auto;
+
+    @media (min-width: 1024px) {
+      padding: 2rem;
+    }
+  }
+
+  // Icons
+  &__icon {
+    width: 1.5rem;
+    height: 1.5rem;
+
+    &--sm {
+      width: 1rem;
+      height: 1rem;
+      color: #9ca3af;
+    }
+  }
 }
 
+// Transitions
 .fade-enter-active,
 .fade-leave-active {
-  transition: opacity var(--transition-base) ease;
+  transition: opacity 150ms ease;
 }
 
 .fade-enter-from,

+ 37 - 2
src/locales/en.json

@@ -22,5 +22,40 @@
   "请输入新密码": "Please enter the new password",
   "两次输入的密码不一致": "The passwords entered twice do not match",
   "取消": "Cancel",
-  "确定": "Confirm"
-}
+  "确定": "Confirm",
+  "欢迎回来": "Welcome Back",
+  "登录您的管理后台,开始管理您的业务": "Sign in to your admin console and start managing your business",
+  "用户": "Users",
+  "稳定性": "Uptime",
+  "技术支持": "Support",
+  "登录": "Sign In",
+  "用户名": "Username",
+  "密码": "Password",
+  "记住我": "Remember me",
+  "忘记密码?": "Forgot password?",
+  "登录成功": "Login successful",
+  "登录失败": "Login failed",
+  "登录失败,请检查网络": "Login failed, please check your network",
+  "请联系管理员重置密码": "Please contact the administrator to reset your password",
+  "请输入用户名": "Please enter username",
+  "请输入密码": "Please enter password",
+  "欢迎回来,这是您的数据概览": "Welcome back, here is your data overview",
+  "机器总数": "Total Machines",
+  "摄像头总数": "Total Cameras",
+  "通道总数": "Total Channels",
+  "摄像头在线率": "Camera Online Rate",
+  "已启用": "Enabled",
+  "已禁用": "Disabled",
+  "在线": "Online",
+  "离线": "Offline",
+  "可用通道数量": "Available channels",
+  "系统运行正常": "System running normally",
+  "快捷操作": "Quick Actions",
+  "刷新数据": "Refresh",
+  "系统信息": "System Info",
+  "系统状态": "System Status",
+  "数据更新时间": "Last Updated",
+  "版本": "Version",
+  "正常": "Normal",
+  "获取统计数据失败": "Failed to get statistics"
+}

+ 37 - 2
src/locales/zh-cn.json

@@ -22,5 +22,40 @@
   "请输入新密码": "请输入新密码",
   "两次输入的密码不一致": "两次输入的密码不一致",
   "取消": "取消",
-  "确定": "确定"
-}
+  "确定": "确定",
+  "欢迎回来": "欢迎回来",
+  "登录您的管理后台,开始管理您的业务": "登录您的管理后台,开始管理您的业务",
+  "用户": "用户",
+  "稳定性": "稳定性",
+  "技术支持": "技术支持",
+  "登录": "登录",
+  "用户名": "用户名",
+  "密码": "密码",
+  "记住我": "记住我",
+  "忘记密码?": "忘记密码?",
+  "登录成功": "登录成功",
+  "登录失败": "登录失败",
+  "登录失败,请检查网络": "登录失败,请检查网络",
+  "请联系管理员重置密码": "请联系管理员重置密码",
+  "请输入用户名": "请输入用户名",
+  "请输入密码": "请输入密码",
+  "欢迎回来,这是您的数据概览": "欢迎回来,这是您的数据概览",
+  "机器总数": "机器总数",
+  "摄像头总数": "摄像头总数",
+  "通道总数": "通道总数",
+  "摄像头在线率": "摄像头在线率",
+  "已启用": "已启用",
+  "已禁用": "已禁用",
+  "在线": "在线",
+  "离线": "离线",
+  "可用通道数量": "可用通道数量",
+  "系统运行正常": "系统运行正常",
+  "快捷操作": "快捷操作",
+  "刷新数据": "刷新数据",
+  "系统信息": "系统信息",
+  "系统状态": "系统状态",
+  "数据更新时间": "数据更新时间",
+  "版本": "版本",
+  "正常": "正常",
+  "获取统计数据失败": "获取统计数据失败"
+}

+ 10 - 0
src/store/app.ts

@@ -32,6 +32,14 @@ export const useAppStore = defineStore('app', () => {
     sidebarOpened.value = !sidebarOpened.value
   }
 
+  function closeSidebar() {
+    sidebarOpened.value = false
+  }
+
+  function openSidebar() {
+    sidebarOpened.value = true
+  }
+
   function setLoading(value: boolean) {
     loading.value = value
   }
@@ -58,6 +66,8 @@ export const useAppStore = defineStore('app', () => {
     locale,
     size,
     toggleSidebar,
+    closeSidebar,
+    openSidebar,
     setLoading,
     changeLanguage,
     setSize

+ 409 - 207
src/views/dashboard/index.vue

@@ -1,141 +1,201 @@
 <template>
-  <div class="dashboard-container">
-    <!-- 统计卡片 -->
-    <el-row :gutter="20" class="stat-cards">
-      <el-col :xs="24" :sm="12" :lg="6">
-        <el-card class="stat-card machines" shadow="hover">
-          <div class="stat-content">
-            <div class="stat-icon">
-              <el-icon :size="40"><Monitor /></el-icon>
-            </div>
-            <div class="stat-info">
-              <div class="stat-value">
-                {{ dashboardData?.machineTotal || 0 }}
-              </div>
-              <div class="stat-label">机器总数</div>
-            </div>
-          </div>
-          <div class="stat-footer">
-            <span class="online">已启用: {{ dashboardData?.machineEnabled || 0 }}</span>
-            <span class="offline">
-              已禁用: {{ (dashboardData?.machineTotal || 0) - (dashboardData?.machineEnabled || 0) }}
+  <div class="dashboard">
+    <!-- Page Header -->
+    <div class="dashboard__header">
+      <h1 class="dashboard__title">{{ t('仪表盘') }}</h1>
+      <p class="dashboard__subtitle">{{ t('欢迎回来,这是您的数据概览') }}</p>
+    </div>
+
+    <!-- Stats Cards -->
+    <div class="dashboard__stats">
+      <!-- Machine Card -->
+      <div class="dashboard__card">
+        <div class="dashboard__card-header">
+          <span class="dashboard__card-label">{{ t('机器总数') }}</span>
+          <svg class="dashboard__card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+            <path
+              stroke-linecap="round"
+              stroke-linejoin="round"
+              stroke-width="2"
+              d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
+            />
+          </svg>
+        </div>
+        <div class="dashboard__card-value">{{ dashboardData?.machineTotal || 0 }}</div>
+        <div class="dashboard__card-footer">
+          <span class="dashboard__card-stat dashboard__card-stat--success">
+            {{ t('已启用') }}: {{ dashboardData?.machineEnabled || 0 }}
+          </span>
+          <span class="dashboard__card-stat dashboard__card-stat--muted">
+            {{ t('已禁用') }}: {{ (dashboardData?.machineTotal || 0) - (dashboardData?.machineEnabled || 0) }}
+          </span>
+        </div>
+      </div>
+
+      <!-- Camera Card -->
+      <div class="dashboard__card">
+        <div class="dashboard__card-header">
+          <span class="dashboard__card-label">{{ t('摄像头总数') }}</span>
+          <svg class="dashboard__card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+            <path
+              stroke-linecap="round"
+              stroke-linejoin="round"
+              stroke-width="2"
+              d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
+            />
+          </svg>
+        </div>
+        <div class="dashboard__card-value">{{ dashboardData?.cameraTotal || 0 }}</div>
+        <div class="dashboard__card-footer">
+          <span class="dashboard__card-stat dashboard__card-stat--success">
+            {{ t('在线') }}: {{ dashboardData?.cameraOnline || 0 }}
+          </span>
+          <span class="dashboard__card-stat dashboard__card-stat--muted">
+            {{ t('离线') }}: {{ dashboardData?.cameraOffline || 0 }}
+          </span>
+        </div>
+      </div>
+
+      <!-- Channel Card -->
+      <div class="dashboard__card">
+        <div class="dashboard__card-header">
+          <span class="dashboard__card-label">{{ t('通道总数') }}</span>
+          <svg class="dashboard__card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+            <path
+              stroke-linecap="round"
+              stroke-linejoin="round"
+              stroke-width="2"
+              d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
+            />
+          </svg>
+        </div>
+        <div class="dashboard__card-value">{{ dashboardData?.channelTotal || 0 }}</div>
+        <div class="dashboard__card-footer">
+          <span class="dashboard__card-stat dashboard__card-stat--muted">{{ t('可用通道数量') }}</span>
+        </div>
+      </div>
+
+      <!-- Online Rate Card -->
+      <div class="dashboard__card">
+        <div class="dashboard__card-header">
+          <span class="dashboard__card-label">{{ t('摄像头在线率') }}</span>
+          <svg class="dashboard__card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+            <path
+              stroke-linecap="round"
+              stroke-linejoin="round"
+              stroke-width="2"
+              d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"
+            />
+          </svg>
+        </div>
+        <div class="dashboard__card-value dashboard__card-value--success">{{ onlineRate }}%</div>
+        <div class="dashboard__card-footer">
+          <span class="dashboard__card-stat dashboard__card-stat--muted">{{ t('系统运行正常') }}</span>
+        </div>
+      </div>
+    </div>
+
+    <!-- Quick Actions & System Info -->
+    <div class="dashboard__grid">
+      <!-- Quick Actions -->
+      <div class="dashboard__section">
+        <div class="dashboard__section-header">
+          <h2 class="dashboard__section-title">{{ t('快捷操作') }}</h2>
+          <button class="dashboard__refresh" @click="loadDashboardData">
+            <svg
+              class="dashboard__refresh-icon"
+              :class="{ 'dashboard__refresh-icon--loading': loading }"
+              fill="none"
+              stroke="currentColor"
+              viewBox="0 0 24 24"
+            >
+              <path
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="2"
+                d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
+              />
+            </svg>
+            {{ t('刷新数据') }}
+          </button>
+        </div>
+        <div class="dashboard__actions">
+          <button class="dashboard__action" @click="goTo('/camera')">
+            <svg class="dashboard__action-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="2"
+                d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
+              />
+            </svg>
+            <span>{{ t('摄像头管理') }}</span>
+          </button>
+          <button class="dashboard__action" @click="goTo('/machine')">
+            <svg class="dashboard__action-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="2"
+                d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
+              />
+            </svg>
+            <span>{{ t('机器管理') }}</span>
+          </button>
+          <button class="dashboard__action" @click="goTo('/stream-test')">
+            <svg class="dashboard__action-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="2"
+                d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"
+              />
+              <path
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="2"
+                d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
+              />
+            </svg>
+            <span>{{ t('Stream 测试') }}</span>
+          </button>
+          <button class="dashboard__action" @click="goTo('/stats')">
+            <svg class="dashboard__action-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="2"
+                d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
+              />
+            </svg>
+            <span>{{ t('观看统计') }}</span>
+          </button>
+        </div>
+      </div>
+
+      <!-- System Info -->
+      <div class="dashboard__section">
+        <div class="dashboard__section-header">
+          <h2 class="dashboard__section-title">{{ t('系统信息') }}</h2>
+        </div>
+        <div class="dashboard__info">
+          <div class="dashboard__info-item">
+            <span class="dashboard__info-label">{{ t('系统状态') }}</span>
+            <span class="dashboard__info-value">
+              <span class="dashboard__badge dashboard__badge--success">{{ t('正常') }}</span>
             </span>
           </div>
-        </el-card>
-      </el-col>
-
-      <el-col :xs="24" :sm="12" :lg="6">
-        <el-card class="stat-card cameras" shadow="hover">
-          <div class="stat-content">
-            <div class="stat-icon">
-              <el-icon :size="40"><VideoCamera /></el-icon>
-            </div>
-            <div class="stat-info">
-              <div class="stat-value">
-                {{ dashboardData?.cameraTotal || 0 }}
-              </div>
-              <div class="stat-label">摄像头总数</div>
-            </div>
-          </div>
-          <div class="stat-footer">
-            <span class="online">在线: {{ dashboardData?.cameraOnline || 0 }}</span>
-            <span class="offline">离线: {{ dashboardData?.cameraOffline || 0 }}</span>
-          </div>
-        </el-card>
-      </el-col>
-
-      <el-col :xs="24" :sm="12" :lg="6">
-        <el-card class="stat-card channels" shadow="hover">
-          <div class="stat-content">
-            <div class="stat-icon">
-              <el-icon :size="40"><Connection /></el-icon>
-            </div>
-            <div class="stat-info">
-              <div class="stat-value">
-                {{ dashboardData?.channelTotal || 0 }}
-              </div>
-              <div class="stat-label">通道总数</div>
-            </div>
-          </div>
-          <div class="stat-footer">
-            <span>可用通道数量</span>
-          </div>
-        </el-card>
-      </el-col>
-
-      <el-col :xs="24" :sm="12" :lg="6">
-        <el-card class="stat-card status" shadow="hover">
-          <div class="stat-content">
-            <div class="stat-icon">
-              <el-icon :size="40"><CircleCheck /></el-icon>
-            </div>
-            <div class="stat-info">
-              <div class="stat-value online-rate">{{ onlineRate }}%</div>
-              <div class="stat-label">摄像头在线率</div>
-            </div>
+          <div class="dashboard__info-item">
+            <span class="dashboard__info-label">{{ t('数据更新时间') }}</span>
+            <span class="dashboard__info-value">{{ lastUpdateTime }}</span>
           </div>
-          <div class="stat-footer">
-            <span>系统运行正常</span>
+          <div class="dashboard__info-item">
+            <span class="dashboard__info-label">{{ t('版本') }}</span>
+            <span class="dashboard__info-value">v{{ version }}</span>
           </div>
-        </el-card>
-      </el-col>
-    </el-row>
-
-    <!-- 快捷操作 -->
-    <el-row :gutter="20" class="quick-actions">
-      <el-col :span="24">
-        <el-card shadow="hover">
-          <template #header>
-            <div class="card-header">
-              <span>快捷操作</span>
-              <el-button type="primary" link :icon="Refresh" @click="loadDashboardData">刷新数据</el-button>
-            </div>
-          </template>
-          <el-row :gutter="20">
-            <el-col :xs="24" :sm="8" :md="6">
-              <el-button class="action-btn" @click="goTo('/camera')">
-                <el-icon :size="24"><VideoCamera /></el-icon>
-                <span>摄像头管理</span>
-              </el-button>
-            </el-col>
-            <el-col :xs="24" :sm="8" :md="6">
-              <el-button class="action-btn" @click="goTo('/machine')">
-                <el-icon :size="24"><Monitor /></el-icon>
-                <span>机器管理</span>
-              </el-button>
-            </el-col>
-            <el-col :xs="24" :sm="8" :md="6">
-              <el-button class="action-btn" @click="goTo('/stream-test')">
-                <el-icon :size="24"><VideoPlay /></el-icon>
-                <span>Stream 测试</span>
-              </el-button>
-            </el-col>
-          </el-row>
-        </el-card>
-      </el-col>
-    </el-row>
-
-    <!-- 系统信息 -->
-    <el-row :gutter="20" class="system-info">
-      <el-col :span="24">
-        <el-card shadow="hover">
-          <template #header>
-            <div class="card-header">
-              <span>系统信息</span>
-            </div>
-          </template>
-          <el-descriptions :column="3" border>
-            <el-descriptions-item label="系统状态">
-              <el-tag type="success">正常</el-tag>
-            </el-descriptions-item>
-            <el-descriptions-item label="数据更新时间">
-              {{ lastUpdateTime }}
-            </el-descriptions-item>
-            <el-descriptions-item label="版本">v1.0.0</el-descriptions-item>
-          </el-descriptions>
-        </el-card>
-      </el-col>
-    </el-row>
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 
@@ -143,14 +203,16 @@
 import { ref, onMounted, computed } from 'vue'
 import { useRouter } from 'vue-router'
 import { ElMessage } from 'element-plus'
-import { Monitor, VideoCamera, Connection, CircleCheck, Refresh, VideoPlay } from '@element-plus/icons-vue'
 import { getDashboardStats } from '@/api/stats'
 import type { DashboardStatsDTO } from '@/types'
+import { useI18n } from 'vue-i18n'
 
+const { t } = useI18n()
 const router = useRouter()
 const loading = ref(false)
 const dashboardData = ref<DashboardStatsDTO | null>(null)
 const lastUpdate = ref<Date | null>(null)
+const version = __APP_VERSION__
 
 const lastUpdateTime = computed(() => {
   if (!lastUpdate.value) return '-'
@@ -174,11 +236,11 @@ async function loadDashboardData() {
       dashboardData.value = res.data
       lastUpdate.value = new Date()
     } else {
-      ElMessage.error(res.message || '获取统计数据失败')
+      ElMessage.error(res.message || t('获取统计数据失败'))
     }
   } catch (error) {
     console.error('Failed to load dashboard data:', error)
-    ElMessage.error('获取统计数据失败')
+    ElMessage.error(t('获取统计数据失败'))
   } finally {
     loading.value = false
   }
@@ -190,120 +252,260 @@ onMounted(() => {
 </script>
 
 <style lang="scss" scoped>
-.dashboard-container {
-  // padding: 20px;
-}
+.dashboard {
+  font-family: 'Inter', system-ui, -apple-system, sans-serif;
+
+  // Page Header
+  &__header {
+    margin-bottom: 2rem;
+  }
 
-.stat-cards {
-  margin-bottom: 20px;
+  &__title {
+    font-size: 1.5rem;
+    font-weight: 700;
+    color: #000000;
+    margin: 0 0 0.25rem 0;
 
-  .el-col {
-    margin-bottom: 20px;
+    @media (min-width: 1024px) {
+      font-size: 1.875rem;
+    }
   }
-}
 
-.stat-card {
-  height: 160px;
+  &__subtitle {
+    color: #9ca3af;
+    font-size: 0.875rem;
+    margin: 0;
 
-  .stat-content {
-    display: flex;
-    align-items: center;
-    padding: 10px 0;
+    @media (min-width: 1024px) {
+      font-size: 1rem;
+    }
+  }
+
+  // Stats Cards
+  &__stats {
+    display: grid;
+    grid-template-columns: 1fr;
+    gap: 1rem;
+    margin-bottom: 2rem;
+
+    @media (min-width: 640px) {
+      grid-template-columns: repeat(2, 1fr);
+    }
+
+    @media (min-width: 1024px) {
+      grid-template-columns: repeat(4, 1fr);
+      gap: 1.5rem;
+    }
   }
 
-  .stat-icon {
-    width: 80px;
-    height: 80px;
-    border-radius: 50%;
+  &__card {
+    background: #ffffff;
+    border: 1px solid #e5e7eb;
+    padding: 1.5rem;
+    transition: box-shadow 150ms ease-in-out;
+
+    &:hover {
+      box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+    }
+  }
+
+  &__card-header {
     display: flex;
     align-items: center;
-    justify-content: center;
-    margin-right: 20px;
+    justify-content: space-between;
+    margin-bottom: 1rem;
+  }
+
+  &__card-label {
+    color: #6b7280;
+    font-size: 0.875rem;
   }
 
-  .stat-info {
-    flex: 1;
+  &__card-icon {
+    width: 1.25rem;
+    height: 1.25rem;
+    color: #9ca3af;
   }
 
-  .stat-value {
-    font-size: 32px;
-    font-weight: bold;
-    line-height: 1.2;
+  &__card-value {
+    font-size: 1.875rem;
+    font-weight: 700;
+    color: #000000;
 
-    &.online-rate {
-      color: #67c23a;
+    &--success {
+      color: #10b981;
     }
   }
 
-  .stat-label {
-    font-size: 14px;
-    color: #909399;
-    margin-top: 5px;
+  &__card-footer {
+    display: flex;
+    gap: 1rem;
+    margin-top: 0.5rem;
+    font-size: 0.875rem;
   }
 
-  .stat-footer {
-    display: flex;
-    justify-content: space-between;
-    padding-top: 10px;
-    border-top: 1px solid #ebeef5;
-    font-size: 12px;
-    color: #909399;
+  &__card-stat {
+    &--success {
+      color: #10b981;
+    }
 
-    .online {
-      color: #67c23a;
+    &--muted {
+      color: #9ca3af;
     }
-    .offline {
-      color: #909399;
+  }
+
+  // Grid Layout
+  &__grid {
+    display: grid;
+    grid-template-columns: 1fr;
+    gap: 1.5rem;
+
+    @media (min-width: 1024px) {
+      grid-template-columns: 2fr 1fr;
     }
   }
 
-  &.machines .stat-icon {
-    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-    color: #fff;
+  // Section
+  &__section {
+    background: #ffffff;
+    border: 1px solid #e5e7eb;
+    padding: 1.5rem;
   }
 
-  &.cameras .stat-icon {
-    background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
-    color: #fff;
+  &__section-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 1.5rem;
   }
 
-  &.channels .stat-icon {
-    background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
-    color: #fff;
+  &__section-title {
+    font-size: 1.125rem;
+    font-weight: 600;
+    color: #000000;
+    margin: 0;
   }
 
-  &.status .stat-icon {
-    background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
-    color: #fff;
+  // Refresh Button
+  &__refresh {
+    display: flex;
+    align-items: center;
+    gap: 0.5rem;
+    background: none;
+    border: none;
+    color: #6b7280;
+    font-size: 0.875rem;
+    cursor: pointer;
+    padding: 0.5rem;
+    transition: color 150ms ease-in-out;
+
+    &:hover {
+      color: #000000;
+    }
   }
-}
 
-.quick-actions {
-  margin-bottom: 20px;
+  &__refresh-icon {
+    width: 1rem;
+    height: 1rem;
+
+    &--loading {
+      animation: spin 1s linear infinite;
+    }
+  }
+
+  // Quick Actions
+  &__actions {
+    display: grid;
+    grid-template-columns: repeat(2, 1fr);
+    gap: 1rem;
+
+    @media (min-width: 640px) {
+      grid-template-columns: repeat(4, 1fr);
+    }
+  }
 
-  .action-btn {
-    width: 100%;
-    height: 80px;
+  &__action {
     display: flex;
     flex-direction: column;
     align-items: center;
     justify-content: center;
-    gap: 8px;
-    margin-bottom: 10px;
+    gap: 0.75rem;
+    padding: 1.5rem 1rem;
+    background: #f9fafb;
+    border: 1px solid #e5e7eb;
+    cursor: pointer;
+    transition: all 150ms ease-in-out;
+
+    &:hover {
+      background: #f3f4f6;
+      border-color: #000000;
+    }
 
     span {
-      font-size: 14px;
+      font-size: 0.875rem;
+      color: #374151;
+      font-weight: 500;
     }
   }
-}
 
-.system-info {
-  margin-bottom: 20px;
+  &__action-icon {
+    width: 1.5rem;
+    height: 1.5rem;
+    color: #6b7280;
+  }
+
+  // System Info
+  &__info {
+    display: flex;
+    flex-direction: column;
+    gap: 1rem;
+  }
+
+  &__info-item {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding-bottom: 1rem;
+    border-bottom: 1px solid #f3f4f6;
+
+    &:last-child {
+      border-bottom: none;
+      padding-bottom: 0;
+    }
+  }
+
+  &__info-label {
+    color: #6b7280;
+    font-size: 0.875rem;
+  }
+
+  &__info-value {
+    color: #000000;
+    font-size: 0.875rem;
+    font-weight: 500;
+  }
+
+  // Badge
+  &__badge {
+    display: inline-flex;
+    align-items: center;
+    padding: 0.25rem 0.625rem;
+    font-size: 0.75rem;
+    font-weight: 500;
+
+    &--success {
+      background: #d1fae5;
+      color: #065f46;
+    }
+  }
 }
 
-.card-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
+@keyframes spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
 }
 </style>

+ 622 - 157
src/views/login/index.vue

@@ -5,57 +5,174 @@
       <LangDropdown />
     </div>
 
-    <div class="login__card">
-      <div class="login__brand">
-        <img class="login__logo" src="@/assets/logo.svg" alt="logo" />
-        <div class="login__titles">
-          <h1 class="login__title">{{ t('摄像头管理系统') }}</h1>
-          <p class="login__subtitle">Sign in to your console</p>
+    <!-- Mobile: Single column | Desktop: Split screen -->
+    <div class="login__container">
+      <!-- Left Panel - Hidden on mobile, visible on lg+ -->
+      <div class="login__left">
+        <!-- Logo -->
+        <div class="login__left-logo">
+          <div class="login__left-logo-icon">
+            <svg class="login__icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="2"
+                d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
+              />
+            </svg>
+          </div>
+          <span class="login__left-logo-text">{{ t('摄像头管理系统') }}</span>
         </div>
-      </div>
 
-      <el-form ref="loginFormRef" :model="loginForm" :rules="loginRules" class="login__form" label-position="top">
-        <el-form-item prop="username">
-          <el-input
-            v-model="loginForm.username"
-            placeholder="请输入用户名"
-            size="large"
-            :prefix-icon="User"
-            autocomplete="username"
-          />
-        </el-form-item>
-        <el-form-item prop="password">
-          <el-input
-            v-model="loginForm.password"
-            type="password"
-            placeholder="请输入密码"
-            size="large"
-            :prefix-icon="Lock"
-            show-password
-            @keyup.enter="handleLogin"
-            autocomplete="current-password"
-          />
-        </el-form-item>
-        <div class="login__actions">
-          <el-checkbox v-model="rememberMe">记住我</el-checkbox>
-          <span class="login__forgot" @click="goHelp">忘记密码?</span>
+        <!-- Center content -->
+        <div class="login__left-content">
+          <h2 class="login__left-title">
+            {{ t('欢迎回来') }}
+          </h2>
+          <p class="login__left-desc">
+            {{ t('登录您的管理后台,开始管理您的业务') }}
+          </p>
+
+          <!-- Stats -->
+          <div class="login__left-stats">
+            <div class="login__left-stat">
+              <div class="login__left-stat-value">50K+</div>
+              <div class="login__left-stat-label">{{ t('用户') }}</div>
+            </div>
+            <div class="login__left-stat">
+              <div class="login__left-stat-value">99.9%</div>
+              <div class="login__left-stat-label">{{ t('稳定性') }}</div>
+            </div>
+            <div class="login__left-stat">
+              <div class="login__left-stat-value">24/7</div>
+              <div class="login__left-stat-label">{{ t('技术支持') }}</div>
+            </div>
+          </div>
         </div>
 
-        <el-button type="primary" size="large" :loading="loading" class="login__submit" @click="handleLogin">
-          登 录
-        </el-button>
-      </el-form>
-      <div class="login__version">v{{ version }}</div>
+        <!-- Footer -->
+        <div class="login__left-footer">&copy; {{ new Date().getFullYear() }} TG Live Game. All rights reserved.</div>
+      </div>
+
+      <!-- Right Panel - Login Form -->
+      <div class="login__right">
+        <div class="login__form-wrapper">
+          <!-- Mobile Logo -->
+          <div class="login__mobile-logo">
+            <div class="login__mobile-logo-icon">
+              <svg class="login__icon login__icon--white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                <path
+                  stroke-linecap="round"
+                  stroke-linejoin="round"
+                  stroke-width="2"
+                  d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
+                />
+              </svg>
+            </div>
+            <span class="login__mobile-logo-text">{{ t('摄像头管理系统') }}</span>
+          </div>
+
+          <!-- Title -->
+          <div class="login__title-section">
+            <h1 class="login__title">{{ t('登录') }}</h1>
+            <p class="login__subtitle">{{ t('欢迎回来') }}</p>
+          </div>
+
+          <!-- Form -->
+          <form class="login__form" @submit.prevent="handleLogin">
+            <div class="login__form-item">
+              <input
+                v-model="loginForm.username"
+                type="text"
+                :placeholder="t('用户名')"
+                class="login__input"
+                autocomplete="username"
+              />
+              <p v-if="errors.username" class="login__error">{{ errors.username }}</p>
+            </div>
+
+            <div class="login__form-item">
+              <div class="login__password-wrapper">
+                <input
+                  v-model="loginForm.password"
+                  :type="showPassword ? 'text' : 'password'"
+                  :placeholder="t('密码')"
+                  class="login__input"
+                  autocomplete="current-password"
+                  @keyup.enter="handleLogin"
+                />
+                <button type="button" class="login__password-toggle" @click="showPassword = !showPassword">
+                  <svg
+                    v-if="!showPassword"
+                    class="login__icon login__icon--sm"
+                    fill="none"
+                    stroke="currentColor"
+                    viewBox="0 0 24 24"
+                  >
+                    <path
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                      stroke-width="2"
+                      d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
+                    />
+                    <path
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                      stroke-width="2"
+                      d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
+                    />
+                  </svg>
+                  <svg v-else class="login__icon login__icon--sm" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                    <path
+                      stroke-linecap="round"
+                      stroke-linejoin="round"
+                      stroke-width="2"
+                      d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
+                    />
+                  </svg>
+                </button>
+              </div>
+              <p v-if="errors.password" class="login__error">{{ errors.password }}</p>
+            </div>
+
+            <div class="login__actions">
+              <label class="login__remember">
+                <input v-model="rememberMe" type="checkbox" class="login__checkbox" />
+                <span>{{ t('记住我') }}</span>
+              </label>
+              <span class="login__forgot" @click="goHelp">{{ t('忘记密码?') }}</span>
+            </div>
+
+            <button type="submit" class="login__submit" :disabled="loading">
+              <span v-if="loading" class="login__loading">
+                <svg class="login__spinner" viewBox="0 0 24 24" fill="none">
+                  <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
+                  <path
+                    class="opacity-75"
+                    fill="currentColor"
+                    d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
+                  ></path>
+                </svg>
+              </span>
+              {{ t('登录') }}
+            </button>
+          </form>
+
+          <!-- Version -->
+          <div class="login__version">v{{ version }}</div>
+
+          <!-- Mobile Footer -->
+          <p class="login__mobile-footer">&copy; {{ new Date().getFullYear() }} TG Live Game</p>
+        </div>
+      </div>
     </div>
-    <div class="login__bg" aria-hidden="true"></div>
   </div>
 </template>
 
 <script setup lang="ts">
 import { ref, reactive, onMounted, watch } from 'vue'
 import { useRouter, useRoute } from 'vue-router'
-import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
-import { User, Lock } from '@element-plus/icons-vue'
+import { ElMessage } from 'element-plus'
 import LangDropdown from '@/components/LangDropdown.vue'
 import { useUserStore } from '@/store/user'
 import type { LoginParams } from '@/types'
@@ -68,43 +185,57 @@ const { t } = useI18n()
 
 const version = __APP_VERSION__
 
-const loginFormRef = ref<FormInstance>()
 const loading = ref(false)
 const rememberMe = ref(true)
+const showPassword = ref(false)
 
 const loginForm = reactive<LoginParams>({
   username: '',
   password: ''
 })
 
-const loginRules: FormRules = {
-  username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
-  password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
+const errors = reactive({
+  username: '',
+  password: ''
+})
+
+function validateForm(): boolean {
+  let isValid = true
+  errors.username = ''
+  errors.password = ''
+
+  if (!loginForm.username.trim()) {
+    errors.username = t('请输入用户名')
+    isValid = false
+  }
+
+  if (!loginForm.password.trim()) {
+    errors.password = t('请输入密码')
+    isValid = false
+  }
+
+  return isValid
 }
 
 async function handleLogin() {
-  if (!loginFormRef.value) return
-
-  await loginFormRef.value.validate(async (valid) => {
-    if (valid) {
-      loading.value = true
-      try {
-        const res = await userStore.loginAction(loginForm)
-        if (res.code === 200) {
-          saveLoginInfo()
-          ElMessage.success('登录成功')
-          const redirect = (route.query.redirect as string) || '/'
-          router.push(redirect)
-        } else {
-          ElMessage.error(res.message || '登录失败')
-        }
-      } catch (error: any) {
-        ElMessage.error(error.message || '登录失败,请检查网络')
-      } finally {
-        loading.value = false
-      }
-    }
-  })
+  if (!validateForm()) return
+
+  loading.value = true
+  try {
+    const res = await userStore.loginAction(loginForm)
+    if (res.code === 200) {
+      saveLoginInfo()
+      ElMessage.success(t('登录成功'))
+      const redirect = (route.query.redirect as string) || '/'
+      router.push(redirect)
+    } else {
+      ElMessage.error(res.message || t('登录失败'))
+    }
+  } catch (error: any) {
+    ElMessage.error(error.message || t('登录失败,请检查网络'))
+  } finally {
+    loading.value = false
+  }
 }
 
 // 记住我 - 只保存用户名
@@ -137,7 +268,7 @@ watch(rememberMe, (val) => {
 })
 
 function goHelp() {
-  ElMessage.info('请联系管理员重置密码')
+  ElMessage.info(t('请联系管理员重置密码'))
 }
 </script>
 
@@ -145,14 +276,8 @@ function goHelp() {
 .login {
   position: relative;
   min-height: 100vh;
-  display: grid;
-  grid-template-columns: 1fr;
-  place-items: center;
-  background: radial-gradient(1200px 600px at 100% 0%, rgba(102, 126, 234, 0.25), transparent 60%),
-    radial-gradient(1000px 500px at 0% 100%, rgba(118, 75, 162, 0.25), transparent 60%),
-    linear-gradient(135deg, #0f172a 0%, #111827 60%, #0b1220 100%);
-  padding: 24px;
-  overflow: hidden;
+  background: #ffffff;
+  font-family: 'Inter', system-ui, -apple-system, sans-serif;
 
   &__lang {
     position: absolute;
@@ -160,105 +285,445 @@ function goHelp() {
     right: 24px;
     z-index: 10;
 
-    // 覆盖 LangDropdown 组件的 CSS 变量 (深色主题)
-    --lang-color: #e5e7eb;
-    --lang-hover-bg: rgba(255, 255, 255, 0.18);
-    --lang-hover-color: #fff;
+    --lang-color: #374151;
+    --lang-hover-bg: rgba(0, 0, 0, 0.05);
+    --lang-hover-color: #000;
 
     :deep(.lang-trigger) {
-      background: rgba(255, 255, 255, 0.1);
+      background: rgba(0, 0, 0, 0.05);
       padding: 8px 12px;
       border-radius: 8px;
     }
   }
-}
 
-.login__bg {
-  position: absolute;
-  inset: -10% -10% -10% -10%;
-  background: radial-gradient(800px 400px at 50% -10%, rgba(59, 130, 246, 0.15), transparent 60%),
-    radial-gradient(700px 350px at 100% 100%, rgba(236, 72, 153, 0.12), transparent 60%);
-  filter: blur(40px);
-  pointer-events: none;
-}
+  &__container {
+    min-height: 100vh;
+    display: flex;
+    flex-direction: column;
 
-.login__card {
-  width: 100%;
-  max-width: 450px;
-  padding: 36px 32px 28px;
-  border-radius: 16px;
-  background: rgba(255, 255, 255, 0.06);
-  backdrop-filter: saturate(180%) blur(14px);
-  border: 1px solid rgba(255, 255, 255, 0.12);
-  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.35);
-  color: #e5e7eb;
-}
+    @media (min-width: 1024px) {
+      flex-direction: row;
+    }
+  }
 
-.login__brand {
-  display: flex;
-  align-items: center;
-  gap: 14px;
-  margin-bottom: 18px;
-}
-.login__logo {
-  width: 36px;
-  height: 36px;
-}
-.login__titles {
-  display: grid;
-  gap: 2px;
-}
-.login__title {
-  margin: 0;
-  font-size: 22px;
-  font-weight: 700;
-  color: #f9fafb;
-}
-.login__subtitle {
-  margin: 0;
-  font-size: 13px;
-  color: #9ca3af;
-}
+  // Left Panel
+  &__left {
+    display: none;
 
-.login__form {
-  margin-top: 8px;
-  .el-input__wrapper {
-    background: rgba(255, 255, 255, 0.92);
+    @media (min-width: 1024px) {
+      display: flex;
+      width: 50%;
+      background: linear-gradient(to bottom right, #111827, #1f2937, #000000);
+      padding: 2rem 3rem;
+      flex-direction: column;
+      justify-content: space-between;
+    }
+
+    @media (min-width: 1280px) {
+      width: 40%;
+    }
   }
-  .el-input {
-    height: 44px;
+
+  &__left-logo {
+    display: flex;
+    align-items: center;
+    gap: 0.75rem;
   }
-}
 
-.login__actions {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin: 2px 0 10px;
-  color: #cbd5e1;
-}
-.login__forgot {
-  font-size: 12px;
-  color: #93c5fd;
-  cursor: pointer;
-}
+  &__left-logo-icon {
+    width: 2.5rem;
+    height: 2.5rem;
+    background: #ffffff;
+    border-radius: 0.5rem;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
 
-.login__submit {
-  width: 100%;
-  height: 44px;
-  font-size: 15px;
-}
+  &__left-logo-text {
+    color: #ffffff;
+    font-weight: 600;
+    font-size: 1.125rem;
+  }
+
+  &__left-content {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+  }
+
+  &__left-title {
+    font-size: 1.875rem;
+    font-weight: 700;
+    color: #ffffff;
+    margin-bottom: 1rem;
+    line-height: 1.2;
+
+    @media (min-width: 1280px) {
+      font-size: 2.25rem;
+    }
+  }
+
+  &__left-desc {
+    color: #9ca3af;
+    font-size: 1rem;
+    max-width: 24rem;
+
+    @media (min-width: 1280px) {
+      font-size: 1.125rem;
+    }
+  }
+
+  &__left-stats {
+    display: flex;
+    gap: 2rem;
+    margin-top: 2.5rem;
+  }
+
+  &__left-stat-value {
+    font-size: 1.5rem;
+    font-weight: 700;
+    color: #ffffff;
+
+    @media (min-width: 1280px) {
+      font-size: 1.875rem;
+    }
+  }
+
+  &__left-stat-label {
+    color: #6b7280;
+    font-size: 0.875rem;
+  }
+
+  &__left-footer {
+    color: #4b5563;
+    font-size: 0.875rem;
+  }
+
+  // Right Panel
+  &__right {
+    flex: 1;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 1.5rem;
+
+    @media (min-width: 640px) {
+      padding: 2rem;
+    }
+
+    @media (min-width: 768px) {
+      padding: 3rem;
+    }
+
+    @media (min-width: 1024px) {
+      padding: 4rem;
+    }
+  }
+
+  &__form-wrapper {
+    width: 100%;
+    max-width: 24rem;
+
+    @media (min-width: 640px) {
+      max-width: 28rem;
+    }
+  }
+
+  // Mobile Logo
+  &__mobile-logo {
+    display: flex;
+    align-items: center;
+    gap: 0.5rem;
+    margin-bottom: 2rem;
+
+    @media (min-width: 640px) {
+      margin-bottom: 3rem;
+    }
+
+    @media (min-width: 1024px) {
+      display: none;
+    }
+  }
+
+  &__mobile-logo-icon {
+    width: 2rem;
+    height: 2rem;
+    background: #000000;
+    border-radius: 0.5rem;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+    @media (min-width: 640px) {
+      width: 2.5rem;
+      height: 2.5rem;
+    }
+  }
+
+  &__mobile-logo-text {
+    font-weight: 600;
+    color: #000000;
+    font-size: 1rem;
+
+    @media (min-width: 640px) {
+      font-size: 1.125rem;
+    }
+  }
+
+  // Title
+  &__title-section {
+    margin-bottom: 2rem;
+
+    @media (min-width: 640px) {
+      margin-bottom: 2.5rem;
+    }
+
+    @media (min-width: 1024px) {
+      margin-bottom: 3rem;
+    }
+  }
+
+  &__title {
+    font-size: 1.875rem;
+    font-weight: 800;
+    color: #000000;
+    letter-spacing: -0.025em;
+    line-height: 1;
+    margin: 0 0 0.5rem 0;
+
+    @media (min-width: 640px) {
+      font-size: 2.25rem;
+      margin-bottom: 0.75rem;
+    }
+
+    @media (min-width: 1024px) {
+      font-size: 3rem;
+    }
+  }
+
+  &__subtitle {
+    color: #9ca3af;
+    font-size: 1rem;
+    margin: 0;
+
+    @media (min-width: 640px) {
+      font-size: 1.125rem;
+    }
+  }
+
+  // Form
+  &__form {
+    display: flex;
+    flex-direction: column;
+    gap: 1.5rem;
+
+    @media (min-width: 640px) {
+      gap: 2rem;
+    }
+  }
+
+  &__form-item {
+    position: relative;
+  }
+
+  &__input {
+    width: 100%;
+    padding: 0.75rem 0;
+    background: transparent;
+    border: none;
+    border-bottom: 1px solid #e5e7eb;
+    color: #000000;
+    font-size: 1rem;
+    outline: none;
+    transition: border-color 150ms ease-in-out;
+
+    @media (min-width: 640px) {
+      padding: 1rem 0;
+      font-size: 1.125rem;
+    }
+
+    &::placeholder {
+      color: #d1d5db;
+    }
+
+    &:focus {
+      border-bottom-color: #000000;
+    }
+  }
+
+  &__password-wrapper {
+    position: relative;
+    display: flex;
+    align-items: center;
+  }
+
+  &__password-toggle {
+    position: absolute;
+    right: 0;
+    top: 50%;
+    transform: translateY(-50%);
+    background: none;
+    border: none;
+    padding: 0.5rem;
+    cursor: pointer;
+    color: #9ca3af;
+    transition: color 150ms ease-in-out;
+
+    &:hover {
+      color: #000000;
+    }
+  }
+
+  &__error {
+    color: #ef4444;
+    font-size: 0.75rem;
+    margin-top: 0.5rem;
+  }
+
+  // Actions
+  &__actions {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin: 0.5rem 0 0.5rem;
+    color: #94a3b8;
+    font-size: 0.875rem;
+
+    @media (min-width: 640px) {
+      font-size: 1rem;
+    }
+  }
+
+  &__remember {
+    display: flex;
+    align-items: center;
+    cursor: pointer;
+    transition: color 150ms ease-in-out;
+
+    &:hover {
+      color: #374151;
+    }
+  }
+
+  &__checkbox {
+    width: 1rem;
+    height: 1rem;
+    border-radius: 0.25rem;
+    border: 1px solid #d1d5db;
+    margin-right: 0.5rem;
+    cursor: pointer;
+    accent-color: #000000;
+
+    @media (min-width: 640px) {
+      width: 1.25rem;
+      height: 1.25rem;
+      margin-right: 0.75rem;
+    }
+  }
+
+  &__forgot {
+    color: #000000;
+    font-weight: 500;
+    cursor: pointer;
+    transition: opacity 150ms ease-in-out;
+
+    &:hover {
+      opacity: 0.7;
+      text-decoration: underline;
+      text-underline-offset: 4px;
+    }
+  }
+
+  // Submit
+  &__submit {
+    width: 100%;
+    padding: 0.875rem;
+    background: #000000;
+    color: #ffffff;
+    font-weight: 600;
+    font-size: 1rem;
+    border: none;
+    cursor: pointer;
+    transition: background-color 150ms ease-in-out;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 0.5rem;
+
+    @media (min-width: 640px) {
+      padding: 1rem;
+      font-size: 1.125rem;
+    }
+
+    &:hover:not(:disabled) {
+      background: #1f2937;
+    }
 
-.login__version {
-  margin-top: 16px;
-  text-align: center;
-  font-size: 12px;
-  color: #6b7280;
+    &:active:not(:disabled) {
+      background: #374151;
+    }
+
+    &:disabled {
+      opacity: 0.7;
+      cursor: not-allowed;
+    }
+  }
+
+  &__loading {
+    display: flex;
+    align-items: center;
+  }
+
+  &__spinner {
+    width: 1.25rem;
+    height: 1.25rem;
+    animation: spin 1s linear infinite;
+  }
+
+  // Version
+  &__version {
+    margin-top: 1rem;
+    text-align: center;
+    font-size: 0.75rem;
+    color: #9ca3af;
+  }
+
+  // Mobile Footer
+  &__mobile-footer {
+    text-align: center;
+    color: #9ca3af;
+    font-size: 0.75rem;
+    margin-top: 2rem;
+
+    @media (min-width: 1024px) {
+      display: none;
+    }
+  }
+
+  // Icons
+  &__icon {
+    width: 1.25rem;
+    height: 1.25rem;
+
+    &--white {
+      color: #ffffff;
+    }
+
+    &--sm {
+      width: 1.25rem;
+      height: 1.25rem;
+    }
+  }
 }
 
-@media (max-width: 480px) {
-  .login__card {
-    padding: 28px 20px 22px;
+@keyframes spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
   }
 }
 </style>