Эх сурвалжийг харах

feat(admin): craft immersive control interface

AI-Co-Authored-By: Codex
chendeben 1 сар өмнө
parent
commit
9fde9fd1a4

+ 10 - 10
admin-web/src/app.tsx

@@ -3,37 +3,37 @@ import type { ReactNode } from 'react';
 
 const theme: ThemeConfig = {
   token: {
-    colorPrimary: '#168fce',
-    colorInfo: '#168fce',
+    colorPrimary: '#0f9fdb',
+    colorInfo: '#0f9fdb',
     colorSuccess: '#168a5b',
     colorWarning: '#b36b13',
     colorError: '#d14343',
-    colorBgLayout: '#f3f7fb',
+    colorBgLayout: 'transparent',
     colorBgContainer: '#ffffff',
     colorText: '#162131',
     colorTextSecondary: '#5b6b7d',
     colorBorder: '#d9e3ec',
     colorBorderSecondary: '#e7edf3',
-    borderRadius: 10,
-    borderRadiusLG: 14,
+    borderRadius: 12,
+    borderRadiusLG: 20,
     controlHeight: 40,
     controlHeightLG: 44,
-    boxShadowSecondary: '0 16px 40px rgba(34, 57, 79, 0.12)',
+    boxShadowSecondary: '0 24px 64px rgba(24, 70, 96, 0.14)',
     fontFamily:
       '-apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif',
   },
   components: {
-    Layout: { siderBg: '#111c2b', headerBg: '#ffffff' },
+    Layout: { siderBg: '#0b1726', headerBg: 'rgba(248, 252, 254, 0.72)' },
     Table: {
       headerBg: '#f6f9fc',
       headerColor: '#3b4a5b',
-      rowHoverBg: '#f0f8fc',
+      rowHoverBg: '#edf9fd',
       borderColor: '#e7edf3',
     },
     Menu: {
-      darkItemBg: '#111c2b',
+      darkItemBg: '#0b1726',
       darkItemColor: '#b6c5d5',
-      darkItemHoverBg: 'rgba(43, 156, 216, 0.12)',
+      darkItemHoverBg: 'rgba(43, 156, 216, 0.14)',
       darkItemSelectedBg: 'rgba(43, 156, 216, 0.2)',
       darkItemSelectedColor: '#ffffff',
       itemBorderRadius: 10,

+ 12 - 1
admin-web/src/components/Page.tsx

@@ -1,4 +1,5 @@
 import type { ReactNode } from 'react';
+import { Activity } from 'lucide-react';
 
 export function PageHeader({
   title,
@@ -12,6 +13,10 @@ export function PageHeader({
   return (
     <div className="page-toolbar">
       <div className="page-heading">
+        <div className="page-kicker">
+          <Activity size={13} aria-hidden="true" />
+          <span>OPERATIONS / LIVE INTERFACE</span>
+        </div>
         <h1 className="page-title">{title}</h1>
         {description ? <p className="page-description">{description}</p> : null}
       </div>
@@ -35,7 +40,12 @@ export function Surface({
     <section className={`surface ${className}`.trim()}>
       {title || actions ? (
         <header className="surface-header">
-          {title ? <h2 className="surface-title">{title}</h2> : <span />}
+          {title ? (
+            <h2 className="surface-title">
+              <span className="surface-title-mark" aria-hidden="true" />
+              {title}
+            </h2>
+          ) : <span />}
           {actions}
         </header>
       ) : null}
@@ -59,6 +69,7 @@ export function Metric({
 }) {
   return (
     <div className={`metric metric--${tone}`}>
+      <span className="metric-signal" aria-hidden="true" />
       <div className="metric-copy">
         <div className="metric-label">{label}</div>
         <div className="metric-value">{value}</div>

+ 476 - 90
admin-web/src/global.less

@@ -1,20 +1,24 @@
 :root {
   color-scheme: light;
   font-synthesis: none;
-  --color-brand: #168fce;
-  --color-brand-strong: #107ab2;
-  --color-brand-soft: #e8f5fb;
-  --color-ink: #162131;
-  --color-muted: #5b6b7d;
-  --color-canvas: #f3f7fb;
-  --color-surface: rgba(255, 255, 255, 0.96);
-  --color-border: #dce5ed;
+  --color-brand: #0f9fdb;
+  --color-brand-strong: #0879b6;
+  --color-brand-bright: #6ee7ff;
+  --color-brand-soft: #e1f6ff;
+  --color-ink: #081423;
+  --color-muted: #586a7e;
+  --color-canvas: #edf4f8;
+  --color-surface: rgba(255, 255, 255, 0.84);
+  --color-border: rgba(142, 169, 187, 0.28);
   --color-success: #168a5b;
   --color-warning: #b36b13;
   --color-danger: #d14343;
-  --shadow-surface: 0 1px 2px rgba(34, 57, 79, 0.04), 0 10px 30px rgba(34, 57, 79, 0.06);
-  --shadow-raised: 0 12px 28px rgba(22, 84, 118, 0.12);
-  --motion-standard: 180ms ease-out;
+  --shadow-surface: 0 1px 0 rgba(255, 255, 255, 0.92) inset, 0 18px 48px rgba(24, 70, 96, 0.08);
+  --shadow-raised: 0 1px 0 rgba(255, 255, 255, 0.94) inset, 0 28px 72px rgba(16, 88, 126, 0.16);
+  --motion-standard: 220ms cubic-bezier(0.22, 1, 0.36, 1);
+  --motion-spring: 420ms cubic-bezier(0.16, 1.35, 0.3, 1);
+  --pointer-x: 72vw;
+  --pointer-y: 18vh;
 }
 
 * {
@@ -33,13 +37,68 @@ body {
   min-width: 320px;
   overflow-x: hidden;
   background:
-    radial-gradient(circle at 88% 0%, rgba(85, 190, 230, 0.1), transparent 28rem),
+    linear-gradient(rgba(255, 255, 255, 0.28), rgba(255, 255, 255, 0)),
     var(--color-canvas);
   color: var(--color-ink);
   font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
   line-height: 1.5;
 }
 
+.lucide {
+  stroke-width: 1.8;
+}
+
+.admin-experience {
+  position: relative;
+  min-height: 100%;
+  isolation: isolate;
+}
+
+.ambient-canvas {
+  position: fixed;
+  z-index: -1;
+  inset: 0;
+  overflow: hidden;
+  pointer-events: none;
+  background:
+    radial-gradient(560px circle at var(--pointer-x) var(--pointer-y), rgba(34, 179, 231, 0.18), transparent 58%),
+    linear-gradient(115deg, rgba(255, 255, 255, 0.94), rgba(231, 243, 249, 0.82));
+}
+
+.ambient-canvas::before {
+  position: absolute;
+  inset: 0;
+  background-image:
+    linear-gradient(rgba(44, 91, 119, 0.045) 1px, transparent 1px),
+    linear-gradient(90deg, rgba(44, 91, 119, 0.045) 1px, transparent 1px);
+  background-size: 64px 64px;
+  mask-image: linear-gradient(to bottom, black, transparent 82%);
+  content: '';
+}
+
+.ambient-orbit {
+  position: absolute;
+  display: block;
+  border: 1px solid rgba(15, 159, 219, 0.16);
+  border-radius: 50%;
+}
+
+.ambient-orbit--one {
+  width: 42vw;
+  height: 42vw;
+  top: -26vw;
+  right: -8vw;
+  box-shadow: 0 0 0 7vw rgba(55, 186, 231, 0.03), 0 0 0 14vw rgba(55, 186, 231, 0.025);
+}
+
+.ambient-orbit--two {
+  width: 28vw;
+  height: 28vw;
+  bottom: -20vw;
+  left: 14vw;
+  border-color: rgba(22, 138, 91, 0.1);
+}
+
 ::selection {
   background: rgba(22, 143, 206, 0.2);
 }
@@ -58,55 +117,107 @@ a:focus-visible,
 }
 
 .page-content {
+  position: relative;
   width: 100%;
-  max-width: 1440px;
+  max-width: 1520px;
   margin: 0 auto;
-  padding-bottom: 48px;
-  animation: page-enter 220ms ease-out both;
+  padding-bottom: 64px;
+  animation: page-enter 480ms cubic-bezier(0.22, 1, 0.36, 1) both;
 }
 
 .page-toolbar {
+  position: relative;
   display: flex;
   flex-wrap: wrap;
   align-items: flex-start;
   justify-content: space-between;
-  gap: 16px;
-  margin-bottom: 22px;
+  gap: 24px;
+  margin-bottom: 32px;
+  padding: 12px 0 25px;
+  border-bottom: 1px solid rgba(80, 116, 139, 0.16);
+}
+
+.page-toolbar::after {
+  position: absolute;
+  width: min(28vw, 320px);
+  height: 2px;
+  bottom: -1px;
+  left: 0;
+  background: linear-gradient(90deg, var(--color-brand), transparent);
+  content: '';
 }
 
 .page-heading {
   min-width: 0;
 }
 
+.page-kicker {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 8px;
+  color: var(--color-brand-strong);
+  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+  font-size: 10px;
+  font-weight: 700;
+  letter-spacing: 0.16em;
+}
+
+.page-kicker .lucide {
+  animation: signal-pulse 2.4s ease-in-out infinite;
+}
+
 .page-title {
   margin: 0;
   color: var(--color-ink);
-  font-size: 28px;
-  line-height: 1.25;
-  font-weight: 720;
-  letter-spacing: -0.02em;
+  font-family: "Avenir Next", "SF Pro Display", -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
+  font-size: clamp(38px, 4vw, 64px);
+  line-height: 0.98;
+  font-weight: 760;
+  letter-spacing: -0.055em;
 }
 
 .page-description {
-  max-width: 720px;
-  margin: 7px 0 0;
+  max-width: 680px;
+  margin: 14px 0 0;
   color: var(--color-muted);
-  font-size: 14px;
+  font-size: 15px;
+  line-height: 1.7;
 }
 
 .page-actions {
   display: flex;
   min-height: 40px;
   align-items: center;
+  align-self: flex-end;
 }
 
 .surface {
+  position: relative;
   overflow: hidden;
   background: var(--color-surface);
   border: 1px solid var(--color-border);
-  border-radius: 14px;
+  border-radius: 22px;
   box-shadow: var(--shadow-surface);
-  transition: border-color var(--motion-standard), box-shadow var(--motion-standard);
+  backdrop-filter: blur(20px) saturate(125%);
+  transition: transform var(--motion-spring), border-color var(--motion-standard), box-shadow var(--motion-standard);
+}
+
+.surface::after {
+  position: absolute;
+  width: 180px;
+  height: 180px;
+  top: -130px;
+  right: -100px;
+  background: radial-gradient(circle, rgba(53, 190, 235, 0.12), transparent 68%);
+  content: '';
+  pointer-events: none;
+}
+
+.surface:hover {
+  border-color: rgba(44, 164, 211, 0.34);
+  box-shadow: var(--shadow-raised);
+  transform: translateY(-3px);
 }
 
 .surface-header {
@@ -115,29 +226,42 @@ a:focus-visible,
   align-items: center;
   justify-content: space-between;
   gap: 12px;
-  min-height: 58px;
-  padding: 12px 20px;
-  border-bottom: 1px solid #e7edf3;
+  min-height: 66px;
+  padding: 14px 22px;
+  border-bottom: 1px solid rgba(125, 153, 173, 0.18);
 }
 
 .surface-title {
+  display: flex;
+  align-items: center;
+  gap: 10px;
   margin: 0;
   color: #26384a;
-  font-size: 16px;
+  font-size: 15px;
   line-height: 1.4;
-  font-weight: 650;
+  font-weight: 700;
+  letter-spacing: -0.012em;
+}
+
+.surface-title-mark {
+  width: 8px;
+  height: 8px;
+  background: var(--color-brand);
+  border: 2px solid rgba(255, 255, 255, 0.9);
+  border-radius: 50%;
+  box-shadow: 0 0 0 4px rgba(15, 159, 219, 0.12);
 }
 
 .surface-body {
-  padding: 20px;
+  padding: 22px;
 }
 
 .metric-grid,
 .metrics-grid {
   display: grid;
   grid-template-columns: repeat(4, minmax(0, 1fr));
-  gap: 16px;
-  margin-bottom: 20px;
+  gap: 18px;
+  margin-bottom: 22px;
 }
 
 .metric {
@@ -145,31 +269,48 @@ a:focus-visible,
   position: relative;
   display: flex;
   min-width: 0;
-  min-height: 122px;
+  min-height: 158px;
   align-items: flex-start;
   justify-content: space-between;
   gap: 12px;
   overflow: hidden;
-  padding: 20px;
-  background: linear-gradient(145deg, #ffffff 0%, #fbfdff 100%);
+  padding: 23px;
+  background:
+    linear-gradient(135deg, rgba(255, 255, 255, 0.96), rgba(247, 252, 255, 0.78)),
+    var(--color-surface);
   border: 1px solid var(--color-border);
-  border-radius: 14px;
+  border-radius: 22px;
   box-shadow: var(--shadow-surface);
-  transition: transform var(--motion-standard), border-color var(--motion-standard), box-shadow var(--motion-standard);
+  backdrop-filter: blur(18px) saturate(125%);
+  transition: transform var(--motion-spring), border-color var(--motion-standard), box-shadow var(--motion-standard);
 }
 
 .metric::before {
   position: absolute;
-  inset: 0 auto 0 0;
-  width: 4px;
-  background: var(--metric-accent);
+  width: 150px;
+  height: 150px;
+  right: -74px;
+  bottom: -86px;
+  border: 1px solid color-mix(in srgb, var(--metric-accent) 23%, transparent);
+  border-radius: 50%;
   content: '';
 }
 
+.metric-signal {
+  position: absolute;
+  width: 34px;
+  height: 3px;
+  top: 0;
+  left: 23px;
+  background: var(--metric-accent);
+  border-radius: 0 0 4px 4px;
+  box-shadow: 0 0 18px color-mix(in srgb, var(--metric-accent) 40%, transparent);
+}
+
 .metric:hover {
   border-color: rgba(22, 143, 206, 0.3);
   box-shadow: var(--shadow-raised);
-  transform: translateY(-2px);
+  transform: translateY(-5px) scale(1.008);
 }
 
 .metric--success {
@@ -194,42 +335,90 @@ a:focus-visible,
 
 .metric-label {
   color: var(--color-muted);
-  font-size: 14px;
-  font-weight: 520;
+  font-size: 13px;
+  font-weight: 650;
+  letter-spacing: 0.02em;
 }
 
 .metric-value {
-  margin-top: 10px;
+  margin-top: 14px;
   overflow-wrap: anywhere;
   color: var(--color-ink);
-  font-size: 30px;
-  line-height: 1.1;
-  font-weight: 720;
+  font-family: "Avenir Next", "SF Pro Display", sans-serif;
+  font-size: clamp(34px, 3.1vw, 48px);
+  line-height: 0.95;
+  font-weight: 760;
   font-variant-numeric: tabular-nums;
-  letter-spacing: -0.025em;
+  letter-spacing: -0.055em;
 }
 
 .metric-hint {
-  margin-top: 7px;
+  margin-top: 13px;
   color: #7a8999;
-  font-size: 12px;
+  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+  font-size: 10px;
+  font-weight: 650;
+  letter-spacing: 0.12em;
 }
 
 .metric-icon {
   display: grid;
-  width: 42px;
-  height: 42px;
-  flex: 0 0 42px;
+  width: 48px;
+  height: 48px;
+  flex: 0 0 48px;
   place-items: center;
   color: var(--metric-accent);
-  background: color-mix(in srgb, var(--metric-accent) 11%, white);
-  border-radius: 12px;
+  background: color-mix(in srgb, var(--metric-accent) 10%, white);
+  border: 1px solid color-mix(in srgb, var(--metric-accent) 20%, white);
+  border-radius: 15px;
+  transform: rotate(3deg);
+  transition: transform var(--motion-spring), background var(--motion-standard);
+}
+
+.metric:hover .metric-icon {
+  background: color-mix(in srgb, var(--metric-accent) 16%, white);
+  transform: rotate(0deg) scale(1.08);
 }
 
 .split-grid {
   display: grid;
   grid-template-columns: minmax(0, 1.35fr) minmax(320px, 0.65fr);
-  gap: 20px;
+  gap: 22px;
+  margin-bottom: 22px;
+}
+
+.dashboard-metrics {
+  grid-template-columns: 1.25fr 0.95fr 0.95fr 0.85fr;
+}
+
+.dashboard-primary-surface {
+  min-height: 100%;
+}
+
+.ranking-row {
+  display: grid;
+  grid-template-columns: 38px minmax(0, 1fr) auto;
+  align-items: center;
+  gap: 10px;
+  min-height: 54px;
+  border-bottom: 1px solid rgba(125, 153, 173, 0.16);
+}
+
+.ranking-row:last-child {
+  border-bottom: 0;
+}
+
+.ranking-index {
+  display: grid;
+  width: 28px;
+  height: 28px;
+  place-items: center;
+  color: var(--color-brand-strong);
+  background: var(--color-brand-soft);
+  border-radius: 9px;
+  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+  font-size: 10px;
+  font-weight: 700;
 }
 
 .rule-group {
@@ -290,16 +479,23 @@ a:focus-visible,
 }
 
 .ant-btn:not(.ant-btn-link):not(.ant-btn-text) {
-  box-shadow: 0 1px 2px rgba(34, 57, 79, 0.05);
+  border-color: rgba(122, 153, 173, 0.34);
+  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.9) inset, 0 8px 22px rgba(27, 72, 98, 0.06);
+}
+
+.ant-btn:not(:disabled):active {
+  transform: scale(0.97);
 }
 
 .ant-btn-primary:not(:disabled) {
-  background: linear-gradient(135deg, #1c9bd8, #147fb9);
+  border-color: transparent;
+  background: linear-gradient(135deg, #17ace5, #0879b6);
+  box-shadow: 0 12px 28px rgba(9, 125, 183, 0.24);
 }
 
 .ant-btn-primary:not(:disabled):hover {
-  background: linear-gradient(135deg, #168fce, #106fa4) !important;
-  box-shadow: 0 8px 18px rgba(22, 143, 206, 0.22);
+  background: linear-gradient(135deg, #26b9ef, #096da5) !important;
+  box-shadow: 0 16px 34px rgba(9, 125, 183, 0.3);
 }
 
 .ant-input:hover,
@@ -311,17 +507,22 @@ a:focus-visible,
 
 .ant-table-wrapper .ant-table-container {
   overflow: hidden;
-  border-radius: 10px;
+  border: 1px solid rgba(129, 157, 176, 0.18);
+  border-radius: 14px;
 }
 
 .ant-table-wrapper .ant-table-thead > tr > th {
   color: #3b4a5b;
-  font-size: 13px;
-  font-weight: 650;
+  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+  font-size: 10px;
+  font-weight: 700;
+  letter-spacing: 0.07em;
+  text-transform: uppercase;
 }
 
 .ant-table-wrapper .ant-table-tbody > tr > td {
   border-bottom-color: #edf1f5;
+  transition: background var(--motion-standard);
 }
 
 .ant-tabs .ant-tabs-nav {
@@ -331,13 +532,61 @@ a:focus-visible,
 .ant-tabs .ant-tabs-tab {
   min-height: 44px;
   padding-inline: 4px;
-  font-weight: 550;
+  font-weight: 650;
 }
 
 .ant-alert {
   border-radius: 12px;
 }
 
+.ant-alert.ant-alert-info {
+  color: #31596d;
+  background: #edf9fe;
+  border-color: #bce5f6;
+}
+
+.ant-alert.ant-alert-success {
+  color: #315d4b;
+  background: #effaf5;
+  border-color: #bfe8d4;
+}
+
+.ant-alert.ant-alert-warning {
+  color: #685125;
+  background: #fff8e8;
+  border-color: #f0d9a7;
+}
+
+.ant-alert.ant-alert-error {
+  color: #763c3c;
+  background: #fff2f1;
+  border-color: #f3c5c2;
+}
+
+.ant-alert.ant-alert-info .ant-alert-message,
+.ant-alert.ant-alert-info .ant-alert-icon {
+  color: #075e87;
+}
+
+.ant-alert.ant-alert-success .ant-alert-message,
+.ant-alert.ant-alert-success .ant-alert-icon {
+  color: #09613e;
+}
+
+.ant-alert.ant-alert-warning .ant-alert-message,
+.ant-alert.ant-alert-warning .ant-alert-icon {
+  color: #76510a;
+}
+
+.ant-alert.ant-alert-error .ant-alert-message,
+.ant-alert.ant-alert-error .ant-alert-icon {
+  color: #8c3030;
+}
+
+.ant-alert .ant-alert-description {
+  color: inherit;
+}
+
 .ant-tag {
   margin-inline-end: 6px;
   padding-inline: 7px;
@@ -356,15 +605,23 @@ a:focus-visible,
   box-shadow: 0 24px 64px rgba(22, 33, 49, 0.18);
 }
 
+.ant-modal .ant-modal-content {
+  border-radius: 22px;
+}
+
 .admin-shell .ant-layout-header {
-  border-bottom: 1px solid rgba(220, 229, 237, 0.82);
-  box-shadow: 0 1px 12px rgba(34, 57, 79, 0.045);
-  backdrop-filter: blur(14px);
+  border-bottom: 1px solid rgba(116, 150, 171, 0.18);
+  background: rgba(248, 252, 254, 0.72) !important;
+  box-shadow: 0 12px 44px rgba(29, 76, 103, 0.05);
+  backdrop-filter: blur(24px) saturate(145%);
 }
 
 .admin-shell .ant-pro-sider {
-  border-right: 1px solid rgba(255, 255, 255, 0.04);
-  box-shadow: 8px 0 26px rgba(10, 22, 36, 0.1);
+  border-right: 1px solid rgba(110, 231, 255, 0.08);
+  background:
+    radial-gradient(circle at 15% 8%, rgba(40, 166, 216, 0.18), transparent 24rem),
+    #0b1726 !important;
+  box-shadow: 18px 0 54px rgba(7, 25, 40, 0.13);
 }
 
 .admin-shell .ant-menu-dark {
@@ -373,32 +630,80 @@ a:focus-visible,
 
 .admin-shell .ant-menu-item {
   min-height: 44px;
-  margin-block: 4px;
+  margin-block: 5px;
+  border: 1px solid transparent;
+  transition: color var(--motion-standard), background var(--motion-standard), border-color var(--motion-standard), transform var(--motion-standard) !important;
+}
+
+.admin-shell .ant-menu-item:not(.ant-menu-item-selected):hover {
+  border-color: rgba(110, 231, 255, 0.08);
+  transform: translateX(3px);
+}
+
+.admin-shell .ant-menu-item-selected {
+  border-color: rgba(110, 231, 255, 0.14);
+  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.16), 0 0 24px rgba(15, 159, 219, 0.08) inset;
 }
 
 .admin-shell .ant-pro-layout-content {
-  padding-block: 28px 48px;
+  padding-block: 40px 64px;
+  background: transparent;
 }
 
 .brand-mark {
   display: grid;
-  width: 34px;
-  height: 34px;
+  width: 38px;
+  height: 38px;
   place-items: center;
   color: #ffffff;
-  background: linear-gradient(145deg, #32b5eb, #168fce);
+  background: linear-gradient(145deg, #4dd4ff, #087db9 72%);
   border: 1px solid rgba(255, 255, 255, 0.24);
-  border-radius: 11px;
-  box-shadow: 0 8px 18px rgba(22, 143, 206, 0.25);
+  border-radius: 13px;
+  box-shadow: 0 10px 28px rgba(12, 137, 195, 0.28), 0 0 0 5px rgba(15, 159, 219, 0.06);
+  transform: rotate(-3deg);
+}
+
+.admin-shell .ant-pro-global-header-logo h1 {
+  display: flex;
+  flex-direction: column;
+  color: var(--color-ink);
+  font-weight: 760;
+  letter-spacing: -0.025em;
+}
+
+.admin-shell .ant-pro-global-header-logo h1::after {
+  content: 'BOT OPERATIONS';
+  margin-top: 1px;
+  color: #6d7e90;
+  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+  font-size: 8px;
+  font-weight: 700;
+  letter-spacing: 0.16em;
 }
 
 .nav-button {
   min-height: 40px;
 }
 
+.control-plane {
+  display: inline-flex;
+  min-height: 32px;
+  align-items: center;
+  gap: 7px;
+  padding: 0 11px;
+  color: #557084;
+  border: 1px solid rgba(115, 151, 173, 0.24);
+  border-radius: 999px;
+  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+  font-size: 9px;
+  font-weight: 700;
+  letter-spacing: 0.1em;
+}
+
 .bot-selector .ant-select-selector {
-  background: #f7fafc !important;
-  border-color: #d7e2eb !important;
+  background: rgba(255, 255, 255, 0.62) !important;
+  border-color: rgba(115, 151, 173, 0.28) !important;
+  backdrop-filter: blur(12px);
 }
 
 .account-button {
@@ -422,6 +727,75 @@ a:focus-visible,
   margin-bottom: 20px;
 }
 
+.login-stage {
+  position: relative;
+  min-height: 100dvh;
+  overflow: hidden;
+  background:
+    linear-gradient(120deg, rgba(255, 255, 255, 0.94), rgba(227, 243, 250, 0.74)),
+    var(--color-canvas);
+  isolation: isolate;
+}
+
+.login-stage::before {
+  position: absolute;
+  z-index: -1;
+  inset: 0;
+  background-image:
+    linear-gradient(rgba(37, 100, 135, 0.055) 1px, transparent 1px),
+    linear-gradient(90deg, rgba(37, 100, 135, 0.055) 1px, transparent 1px);
+  background-size: 72px 72px;
+  mask-image: radial-gradient(circle at center, black, transparent 78%);
+  content: '';
+}
+
+.login-stage .ant-pro-form-login-page-container {
+  background: rgba(255, 255, 255, 0.72);
+  backdrop-filter: blur(24px) saturate(130%);
+}
+
+.login-stage .ant-pro-form-login-page-title {
+  font-family: "Avenir Next", "SF Pro Display", -apple-system, BlinkMacSystemFont, sans-serif;
+  font-weight: 760;
+  letter-spacing: -0.045em;
+}
+
+.login-stage .ant-pro-form-login-page-desc {
+  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+  font-size: 9px;
+  font-weight: 700;
+  letter-spacing: 0.12em;
+}
+
+.login-orbit {
+  position: absolute;
+  z-index: -1;
+  display: block;
+  border: 1px solid rgba(15, 159, 219, 0.2);
+  border-radius: 50%;
+}
+
+.login-orbit--one {
+  width: 56vw;
+  height: 56vw;
+  top: -34vw;
+  right: -12vw;
+  box-shadow: 0 0 0 10vw rgba(15, 159, 219, 0.035), 0 0 0 20vw rgba(15, 159, 219, 0.025);
+}
+
+.login-orbit--two {
+  width: 24vw;
+  height: 24vw;
+  bottom: -13vw;
+  left: 8vw;
+  border-color: rgba(22, 138, 91, 0.15);
+}
+
+@keyframes signal-pulse {
+  0%, 100% { opacity: 0.45; transform: scale(0.88); }
+  50% { opacity: 1; transform: scale(1); }
+}
+
 @keyframes page-enter {
   from {
     opacity: 0;
@@ -435,6 +809,10 @@ a:focus-visible,
 }
 
 @media (max-width: 1023px) {
+  .control-plane {
+    display: none;
+  }
+
   .metric-grid,
   .metrics-grid {
     grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -447,30 +825,34 @@ a:focus-visible,
 
 @media (max-width: 575px) {
   .page-content {
-    padding-bottom: 28px;
+    padding-bottom: 36px;
   }
 
   .page-toolbar {
-    margin-bottom: 18px;
+    gap: 16px;
+    margin-bottom: 22px;
+    padding-top: 4px;
+    padding-bottom: 20px;
   }
 
   .page-title {
-    font-size: 24px;
+    font-size: 34px;
+    line-height: 1;
   }
 
   .page-description {
-    margin-top: 5px;
-    font-size: 13px;
+    margin-top: 10px;
+    font-size: 14px;
   }
 
   .metric {
-    min-height: 110px;
-    padding: 15px;
-    border-radius: 12px;
+    min-height: 134px;
+    padding: 18px;
+    border-radius: 17px;
   }
 
   .metric-value {
-    font-size: 25px;
+    font-size: 32px;
   }
 
   .metric-icon {
@@ -487,7 +869,7 @@ a:focus-visible,
 
   .surface,
   .ant-alert {
-    border-radius: 12px;
+    border-radius: 17px;
   }
 
   .ant-pro-layout .ant-pro-layout-content {
@@ -503,6 +885,10 @@ a:focus-visible,
     width: 128px !important;
   }
 
+  .admin-shell .ant-pro-global-header-logo h1::after {
+    display: none;
+  }
+
   .account-name {
     display: none;
   }

+ 27 - 0
admin-web/src/layouts/AdminLayout.tsx

@@ -6,6 +6,7 @@ import {
   BotMessageSquare,
   ChevronDown,
   ClipboardList,
+  Command,
   Gift,
   LayoutDashboard,
   LogOut,
@@ -60,6 +61,22 @@ export default function AdminLayout() {
       .catch((error) => message.error(error.message));
   }, []);
 
+  useEffect(() => {
+    let animationFrame = 0;
+    const updatePointerLight = (event: PointerEvent) => {
+      cancelAnimationFrame(animationFrame);
+      animationFrame = requestAnimationFrame(() => {
+        document.documentElement.style.setProperty('--pointer-x', `${event.clientX}px`);
+        document.documentElement.style.setProperty('--pointer-y', `${event.clientY}px`);
+      });
+    };
+    window.addEventListener('pointermove', updatePointerLight, { passive: true });
+    return () => {
+      cancelAnimationFrame(animationFrame);
+      window.removeEventListener('pointermove', updatePointerLight);
+    };
+  }, []);
+
   useEffect(() => {
     const loadBots = () => {
       apiRequest<{ items: BotProfile[] }>('/bots')
@@ -158,6 +175,11 @@ export default function AdminLayout() {
     <BotAccessContext.Provider
       value={{ bot: selectedProfile, hasPermission, hasAnyPermission }}
     >
+      <div className="admin-experience">
+        <div className="ambient-canvas" aria-hidden="true">
+          <span className="ambient-orbit ambient-orbit--one" />
+          <span className="ambient-orbit ambient-orbit--two" />
+        </div>
       <ProLayout
         className="admin-shell"
         title="Telegram 群管"
@@ -218,6 +240,10 @@ export default function AdminLayout() {
           render: (_, dom) => dom,
         }}
         actionsRender={() => [
+          <span className="control-plane" key="control-plane">
+            <Command size={14} aria-hidden="true" />
+            CONTROL PLANE
+          </span>,
           <Select
             key="bot-selector"
             className="bot-selector"
@@ -240,6 +266,7 @@ export default function AdminLayout() {
           <Outlet />
         </main>
       </ProLayout>
+      </div>
     </BotAccessContext.Provider>
   );
 }

+ 11 - 17
admin-web/src/pages/Dashboard.tsx

@@ -1,7 +1,7 @@
 import { Button, Empty, Skeleton, Table, Tag, Typography, message } from 'antd';
 import type { ColumnsType } from 'antd/es/table';
 import dayjs from 'dayjs';
-import { RefreshCw } from 'lucide-react';
+import { Activity, RefreshCw, Trophy, UsersRound, WalletCards } from 'lucide-react';
 import { useEffect, useState } from 'react';
 
 import { Metric, PageHeader, Surface } from '@/components/Page';
@@ -87,20 +87,21 @@ export default function DashboardPage() {
     <>
       <PageHeader
         title="概览"
+        description="把群组、活动与积分流汇聚成一张实时运营画布。"
         actions={
           <Button icon={<RefreshCw size={16} />} onClick={() => void load()} loading={loading}>
             刷新
           </Button>
         }
       />
-      <div className="metric-grid">
-        <Metric label="可管理群组" value={data.counts.chats} />
-        <Metric label="抽奖总数" value={data.counts.giveaways} />
-        <Metric label="积分账户" value={data.counts.point_accounts} />
-        <Metric label="积分流水" value={data.counts.point_transactions} />
+      <div className="metric-grid dashboard-metrics">
+        <Metric label="可管理群组" value={data.counts.chats} hint="COMMUNITIES" icon={<UsersRound size={22} />} tone="info" />
+        <Metric label="抽奖总数" value={data.counts.giveaways} hint="CAMPAIGNS" icon={<Trophy size={22} />} tone="warning" />
+        <Metric label="积分账户" value={data.counts.point_accounts} hint="ACCOUNTS" icon={<WalletCards size={22} />} tone="success" />
+        <Metric label="积分流水" value={data.counts.point_transactions} hint="LEDGER EVENTS" icon={<Activity size={22} />} />
       </div>
       <div className="split-grid">
-        <Surface title="最近抽奖">
+        <Surface title="最近抽奖" className="dashboard-primary-surface">
           <Table
             rowKey="giveaway_id"
             columns={giveawayColumns}
@@ -110,21 +111,14 @@ export default function DashboardPage() {
             scroll={{ x: 680 }}
           />
         </Surface>
-        <Surface title="积分排行">
+        <Surface title="积分排行" className="dashboard-ranking">
           {data.top_accounts.length ? (
             data.top_accounts.map((account, index) => (
               <div
                 key={`${account.chat_id}-${account.user_id}`}
-                style={{
-                  display: 'grid',
-                  gridTemplateColumns: '28px minmax(0, 1fr) auto',
-                  gap: 8,
-                  alignItems: 'center',
-                  minHeight: 42,
-                  borderBottom: index < data.top_accounts.length - 1 ? '1px solid #eaeef2' : 0,
-                }}
+                className="ranking-row"
               >
-                <Typography.Text type="secondary">{index + 1}</Typography.Text>
+                <span className="ranking-index">{String(index + 1).padStart(2, '0')}</span>
                 <Typography.Text ellipsis>
                   {userDisplayName(account)}
                 </Typography.Text>

+ 7 - 2
admin-web/src/pages/Directory.tsx

@@ -21,6 +21,8 @@ import {
   Settings2,
   UserRoundCheck,
   UserRoundX,
+  Wifi,
+  WifiOff,
 } from 'lucide-react';
 import { useEffect, useState } from 'react';
 
@@ -331,8 +333,11 @@ export default function DirectoryPage() {
       dataIndex: 'online',
       width: 110,
       render: (value) => (
-        <Tag color={value ? 'success' : 'error'}>
-          {value ? '🟢 在线' : '🔴 离线'}
+        <Tag
+          color={value ? 'success' : 'error'}
+          icon={value ? <Wifi size={13} /> : <WifiOff size={13} />}
+        >
+          {value ? '在线' : '离线'}
         </Tag>
       ),
     },

+ 11 - 8
admin-web/src/pages/Login.tsx

@@ -1,7 +1,6 @@
-import { LockOutlined, UserOutlined } from '@ant-design/icons';
 import { LoginFormPage, ProFormText } from '@ant-design/pro-components';
 import { Alert, Typography, message } from 'antd';
-import { Bot } from 'lucide-react';
+import { Bot, LockKeyhole, UserRound } from 'lucide-react';
 import { history } from '@umijs/max';
 import { useState } from 'react';
 
@@ -12,15 +11,18 @@ export default function LoginPage() {
   const [error, setError] = useState('');
 
   return (
+    <div className="login-stage">
+      <span className="login-orbit login-orbit--one" aria-hidden="true" />
+      <span className="login-orbit login-orbit--two" aria-hidden="true" />
     <LoginFormPage
       logo={<Bot size={34} color="#168acd" />}
       title="Telegram 群管"
-      subTitle="本地管理面板"
+      subTitle="BOT OPERATIONS / CONTROL PLANE"
       containerStyle={{
         minHeight: 420,
-        border: '1px solid #d8dee4',
-        borderRadius: 6,
-        boxShadow: '0 8px 24px rgba(23, 33, 43, 0.08)',
+        border: '1px solid rgba(255, 255, 255, 0.7)',
+        borderRadius: 24,
+        boxShadow: '0 32px 80px rgba(20, 55, 78, 0.16)',
       }}
       onFinish={async (values) => {
         setError('');
@@ -46,18 +48,19 @@ export default function LoginPage() {
         name="username"
         label="用户名"
         initialValue="admin"
-        fieldProps={{ prefix: <UserOutlined />, autoComplete: 'username', size: 'large' }}
+        fieldProps={{ prefix: <UserRound size={17} />, autoComplete: 'username', size: 'large' }}
         rules={[{ required: true, message: '请输入用户名' }]}
       />
       <ProFormText.Password
         name="password"
         label="密码"
-        fieldProps={{ prefix: <LockOutlined />, autoComplete: 'current-password', size: 'large' }}
+        fieldProps={{ prefix: <LockKeyhole size={17} />, autoComplete: 'current-password', size: 'large' }}
         rules={[{ required: true, message: '请输入密码' }]}
       />
       <Typography.Text type="secondary">
         默认账号 admin;首次登录后必须修改初始密码。
       </Typography.Text>
     </LoginFormPage>
+    </div>
   );
 }

+ 13 - 0
admin-web/tests/e2e/admin.spec.ts

@@ -456,6 +456,19 @@ test('登录与主要管理视图在不同视口无页面级横向滚动', async
       }
       await expect(page.getByText('允许回复')).toBeVisible();
       await expect(page.getByText('允许标记已读')).toBeVisible();
+      const reminderAlert = page
+        .locator('.ant-alert-info')
+        .filter({ hasText: '第一期按每条用户消息提醒' });
+      const reminderColors = await reminderAlert.evaluate((element) => ({
+        background: getComputedStyle(element).backgroundColor,
+        message: getComputedStyle(element.querySelector('.ant-alert-message') as Element).color,
+        description: getComputedStyle(element.querySelector('.ant-alert-description') as Element).color,
+      }));
+      expect(reminderColors).toEqual({
+        background: 'rgb(237, 249, 254)',
+        message: 'rgb(7, 94, 135)',
+        description: 'rgb(49, 89, 109)',
+      });
       await page.getByRole('tab', { name: '知识来源' }).click();
       await expect(page.getByText('官方公告频道')).toBeVisible();
       await expect(page.getByText('讨论群:官方讨论群')).toBeVisible();

+ 33 - 0
admin-web/tests/unit/IconPolicy.test.ts

@@ -0,0 +1,33 @@
+import { readdirSync, readFileSync } from 'node:fs';
+import { join, resolve } from 'node:path';
+
+import { describe, expect, test } from 'vitest';
+
+const sourceRoot = resolve(process.cwd(), 'src');
+const generatedDirectories = new Set(['.umi', '.umi-production']);
+const emojiPattern = /[\u{1F000}-\u{1FAFF}\u{2600}-\u{27BF}]/u;
+
+function sourceFiles(directory: string): string[] {
+  return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
+    const path = join(directory, entry.name);
+    if (entry.isDirectory()) {
+      return generatedDirectories.has(entry.name) ? [] : sourceFiles(path);
+    }
+    return /\.(?:ts|tsx|less)$/.test(entry.name) ? [path] : [];
+  });
+}
+
+describe('图标与符号规范', () => {
+  test('业务界面仅使用 Lucide 图标且不包含表情符号', () => {
+    const violations = sourceFiles(sourceRoot).flatMap((path) => {
+      const content = readFileSync(path, 'utf8');
+      const reasons = [
+        content.includes('@ant-design/icons') ? '使用了非 Lucide 图标库' : '',
+        emojiPattern.test(content) ? '包含表情符号' : '',
+      ].filter(Boolean);
+      return reasons.map((reason) => `${path}: ${reason}`);
+    });
+
+    expect(violations).toEqual([]);
+  });
+});

+ 1 - 1
admin-web/vitest.config.ts

@@ -12,7 +12,7 @@ export default defineConfig({
   test: {
     environment: 'jsdom',
     setupFiles: ['./tests/setup.ts'],
-    include: ['./tests/unit/**/*.test.tsx'],
+    include: ['./tests/unit/**/*.test.{ts,tsx}'],
     css: true,
   },
 });