refactor(frontend): drop CustomStatistic wrapper, move overrides to theme tokens

- Delete `<CustomStatistic>` (a pass-through wrapper over <Statistic>)
  and its unscoped global `.ant-statistic-*` CSS overrides; consumers
  (IndexPage, ClientsPage, InboundsPage, NodesPage) now import AntD
  `<Statistic>` directly.
- Add Statistic component tokens to ConfigProvider so the title (11px)
  and content (17px) font sizes still apply, without `!important`
  global selectors.
- Move dark / ultra-dark card border colours from `body.dark .ant-card`
  + `html[data-theme='ultra-dark'] .ant-card` selectors into Card
  `colorBorderSecondary` tokens; page-cards.css now only carries the
  custom radius/shadow/transition that has no token equivalent.
- Simplify XrayStatusCard badge: remove the custom `xray-pulse` dot
  keyframe and per-state ring-colour overrides; AntD `<Badge
  status="processing" color={…}>` already pulses the ring in the same
  colour, no extra CSS needed.
This commit is contained in:
MHSanaei 2026-05-25 03:47:49 +02:00
parent 6f9fdb154d
commit 7e5f279284
No known key found for this signature in database
GPG key ID: 7E4060F2FBE5AB7A
11 changed files with 49 additions and 384 deletions

View file

@ -1,52 +0,0 @@
.ant-statistic-content {
font-size: 17px !important;
line-height: 1.4 !important;
font-weight: 600;
}
.ant-statistic-content-value,
.ant-statistic-content-prefix,
.ant-statistic-content-suffix {
font-size: 17px !important;
}
.ant-statistic-content-prefix {
margin-inline-end: 8px !important;
opacity: 0.7;
}
.ant-statistic-content-prefix .anticon {
font-size: 17px !important;
}
.ant-statistic-content-suffix {
font-size: 12px !important;
opacity: 0.55;
margin-inline-start: 4px;
font-weight: 500;
}
.ant-statistic-title {
font-size: 11px !important;
margin-bottom: 6px !important;
letter-spacing: 0.6px;
text-transform: uppercase;
color: rgba(0, 0, 0, 0.55);
font-weight: 500;
}
body.dark .ant-statistic-content {
color: rgba(255, 255, 255, 0.92);
}
body.dark .ant-statistic-title {
color: rgba(255, 255, 255, 0.72);
}
html[data-theme='ultra-dark'] .ant-statistic-content {
color: rgba(255, 255, 255, 0.95);
}
html[data-theme='ultra-dark'] .ant-statistic-title {
color: rgba(255, 255, 255, 0.70);
}

View file

@ -1,14 +0,0 @@
import type { ReactNode } from 'react';
import { Statistic } from 'antd';
import './CustomStatistic.css';
interface CustomStatisticProps {
title?: string;
value?: string | number;
prefix?: ReactNode;
suffix?: ReactNode;
}
export default function CustomStatistic({ title = '', value = '', prefix, suffix }: CustomStatisticProps) {
return <Statistic title={title} value={value} prefix={prefix} suffix={suffix} />;
}

View file

@ -68,10 +68,25 @@ const ULTRA_DARK_MENU_TOKENS = {
darkSubMenuItemBg: '#000',
darkPopupBg: '#101013',
};
const DARK_CARD_TOKENS = {
colorBorderSecondary: 'rgba(255, 255, 255, 0.06)',
};
const ULTRA_DARK_CARD_TOKENS = {
colorBorderSecondary: 'rgba(255, 255, 255, 0.04)',
};
const STATISTIC_TOKENS = {
contentFontSize: 17,
titleFontSize: 11,
};
export function buildAntdThemeConfig(isDark: boolean, isUltra: boolean): ThemeConfig {
if (!isDark) {
return { algorithm: antdTheme.defaultAlgorithm };
return {
algorithm: antdTheme.defaultAlgorithm,
components: {
Statistic: STATISTIC_TOKENS,
},
};
}
return {
algorithm: antdTheme.darkAlgorithm,
@ -79,6 +94,8 @@ export function buildAntdThemeConfig(isDark: boolean, isUltra: boolean): ThemeCo
components: {
Layout: isUltra ? ULTRA_DARK_LAYOUT_TOKENS : DARK_LAYOUT_TOKENS,
Menu: isUltra ? ULTRA_DARK_MENU_TOKENS : DARK_MENU_TOKENS,
Card: isUltra ? ULTRA_DARK_CARD_TOKENS : DARK_CARD_TOKENS,
Statistic: STATISTIC_TOKENS,
},
};
}

View file

@ -18,6 +18,7 @@ import {
Select,
Space,
Spin,
Statistic,
Switch,
Table,
Tag,
@ -49,7 +50,6 @@ import { useClients } from '@/hooks/useClients';
import { useDatepicker } from '@/hooks/useDatepicker';
import type { ClientRecord, InboundOption } from '@/hooks/useClients';
import AppSidebar from '@/components/AppSidebar';
import CustomStatistic from '@/components/CustomStatistic';
import { IntlUtil, SizeFormatter } from '@/utils';
import { setMessageInstance } from '@/utils/messageBus';
import LazyMount from '@/components/LazyMount';
@ -624,7 +624,7 @@ export default function ClientsPage() {
<Card size="small" hoverable className="summary-card">
<Row gutter={[16, 12]}>
<Col xs={12} sm={8} md={4}>
<CustomStatistic title={t('clients')} value={String(summary.total)} prefix={<TeamOutlined />} />
<Statistic title={t('clients')} value={String(summary.total)} prefix={<TeamOutlined />} />
</Col>
<Col xs={12} sm={8} md={4}>
<Popover
@ -632,7 +632,7 @@ export default function ClientsPage() {
open={summary.online.length ? undefined : false}
content={<div className="client-email-list">{summary.online.map((e) => <div key={e}>{e}</div>)}</div>}
>
<CustomStatistic title={t('online')} value={String(summary.online.length)} prefix={<span className="dot dot-blue" />} />
<Statistic title={t('online')} value={String(summary.online.length)} prefix={<span className="dot dot-blue" />} />
</Popover>
</Col>
<Col xs={12} sm={8} md={4}>
@ -641,7 +641,7 @@ export default function ClientsPage() {
open={summary.depleted.length ? undefined : false}
content={<div className="client-email-list">{summary.depleted.map((e) => <div key={e}>{e}</div>)}</div>}
>
<CustomStatistic title={t('depleted')} value={String(summary.depleted.length)} prefix={<span className="dot dot-red" />} />
<Statistic title={t('depleted')} value={String(summary.depleted.length)} prefix={<span className="dot dot-red" />} />
</Popover>
</Col>
<Col xs={12} sm={8} md={4}>
@ -650,7 +650,7 @@ export default function ClientsPage() {
open={summary.expiring.length ? undefined : false}
content={<div className="client-email-list">{summary.expiring.map((e) => <div key={e}>{e}</div>)}</div>}
>
<CustomStatistic title={t('depletingSoon')} value={String(summary.expiring.length)} prefix={<span className="dot dot-orange" />} />
<Statistic title={t('depletingSoon')} value={String(summary.expiring.length)} prefix={<span className="dot dot-orange" />} />
</Popover>
</Col>
<Col xs={12} sm={8} md={4}>
@ -659,11 +659,11 @@ export default function ClientsPage() {
open={summary.deactive.length ? undefined : false}
content={<div className="client-email-list">{summary.deactive.map((e) => <div key={e}>{e}</div>)}</div>}
>
<CustomStatistic title={t('disabled')} value={String(summary.deactive.length)} prefix={<span className="dot dot-gray" />} />
<Statistic title={t('disabled')} value={String(summary.deactive.length)} prefix={<span className="dot dot-gray" />} />
</Popover>
</Col>
<Col xs={12} sm={8} md={4}>
<CustomStatistic title={t('subscription.active')} value={String(summary.active)} prefix={<span className="dot dot-green" />} />
<Statistic title={t('subscription.active')} value={String(summary.active)} prefix={<span className="dot dot-green" />} />
</Col>
</Row>
</Card>

View file

@ -8,6 +8,7 @@ import {
Modal,
Row,
Spin,
Statistic,
message,
} from 'antd';
@ -26,7 +27,6 @@ import { useMediaQuery } from '@/hooks/useMediaQuery';
import { useWebSocket } from '@/hooks/useWebSocket';
import { useNodesQuery } from '@/api/queries/useNodesQuery';
import AppSidebar from '@/components/AppSidebar';
import CustomStatistic from '@/components/CustomStatistic';
const TextModal = lazy(() => import('@/components/TextModal'));
const PromptModal = lazy(() => import('@/components/PromptModal'));
@ -463,21 +463,21 @@ export default function InboundsPage() {
<Card size="small" hoverable className="summary-card">
<Row gutter={[16, 12]}>
<Col xs={12} sm={12} md={8}>
<CustomStatistic
<Statistic
title={t('pages.inbounds.totalDownUp')}
value={`${SizeFormatter.sizeFormat(totals.up)} / ${SizeFormatter.sizeFormat(totals.down)}`}
prefix={<SwapOutlined />}
/>
</Col>
<Col xs={12} sm={12} md={8}>
<CustomStatistic
<Statistic
title={t('pages.inbounds.totalUsage')}
value={SizeFormatter.sizeFormat(totals.up + totals.down)}
prefix={<PieChartOutlined />}
/>
</Col>
<Col xs={24} sm={24} md={8}>
<CustomStatistic
<Statistic
title={t('pages.inbounds.inboundCount')}
value={String(dbInbounds.length)}
prefix={<BarsOutlined />}

View file

@ -40,152 +40,11 @@
min-height: calc(100vh - 120px);
}
.index-page .ant-card {
border-radius: 12px;
border: 1px solid rgba(0, 0, 0, 0.06);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
transition: transform 0.2s ease, box-shadow 0.25s ease, border-color 0.2s ease;
}
body.dark .index-page .ant-card {
border-color: rgba(255, 255, 255, 0.06);
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.03);
}
html[data-theme='ultra-dark'] .index-page .ant-card {
border-color: rgba(255, 255, 255, 0.04);
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.6),
inset 0 1px 0 rgba(255, 255, 255, 0.025);
}
.index-page .ant-card.ant-card-hoverable:hover {
transform: translateY(-2px);
border-color: rgba(0, 0, 0, 0.10);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
}
body.dark .index-page .ant-card.ant-card-hoverable:hover {
border-color: rgba(255, 255, 255, 0.12);
box-shadow:
0 8px 24px rgba(0, 0, 0, 0.5),
inset 0 1px 0 rgba(255, 255, 255, 0.04);
}
html[data-theme='ultra-dark'] .index-page .ant-card.ant-card-hoverable:hover {
border-color: rgba(255, 255, 255, 0.08);
box-shadow:
0 8px 24px rgba(0, 0, 0, 0.75),
inset 0 1px 0 rgba(255, 255, 255, 0.03);
}
.index-page .ant-card .ant-card-head {
min-height: 44px;
padding-inline: 16px;
}
.index-page .ant-card .ant-card-head-title {
font-size: 13px;
font-weight: 600;
letter-spacing: 0.5px;
text-transform: uppercase;
opacity: 0.75;
}
.index-page .ant-card .ant-card-body {
padding: 18px 20px;
}
.index-page .ant-card .ant-card-body > .ant-row > .ant-col {
position: relative;
padding: 4px 6px;
}
@media (min-width: 769px) {
.index-page .ant-card .ant-card-body > .ant-row > .ant-col + .ant-col::before {
content: '';
position: absolute;
left: 0;
top: 10%;
bottom: 10%;
width: 1px;
background: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.10), transparent);
pointer-events: none;
}
}
body.dark .index-page .ant-card .ant-card-body > .ant-row > .ant-col + .ant-col::before {
background: linear-gradient(180deg, transparent, rgba(255, 255, 255, 0.12), transparent);
}
.index-page .ant-card .ant-card-head {
border-bottom-color: rgba(0, 0, 0, 0.06);
}
.index-page .ant-card .ant-card-actions {
border-top-color: rgba(0, 0, 0, 0.06);
background: transparent;
}
.index-page .ant-card .ant-card-actions > li {
border-inline-end-color: rgba(0, 0, 0, 0.06);
}
body.dark .index-page .ant-card .ant-card-head {
border-bottom-color: rgba(255, 255, 255, 0.06);
}
body.dark .index-page .ant-card .ant-card-actions {
border-top-color: rgba(255, 255, 255, 0.06);
}
body.dark .index-page .ant-card .ant-card-actions > li {
border-inline-end-color: rgba(255, 255, 255, 0.06);
}
html[data-theme='ultra-dark'] .index-page .ant-card .ant-card-head {
border-bottom-color: rgba(255, 255, 255, 0.04);
}
html[data-theme='ultra-dark'] .index-page .ant-card .ant-card-actions {
border-top-color: rgba(255, 255, 255, 0.04);
}
html[data-theme='ultra-dark'] .index-page .ant-card .ant-card-actions > li {
border-inline-end-color: rgba(255, 255, 255, 0.04);
}
.index-page .action {
cursor: pointer;
justify-content: center;
max-width: 100%;
padding: 0 8px;
flex-wrap: nowrap;
color: rgba(0, 0, 0, 0.78);
font-weight: 500;
transition: opacity 0.15s ease, transform 0.15s ease, color 0.2s ease;
}
.index-page .action .anticon {
color: rgba(0, 0, 0, 0.72);
}
body.dark .index-page .action {
color: rgba(255, 255, 255, 0.82);
}
body.dark .index-page .action .anticon {
color: rgba(255, 255, 255, 0.75);
}
html[data-theme='ultra-dark'] .index-page .action {
color: rgba(255, 255, 255, 0.86);
}
html[data-theme='ultra-dark'] .index-page .action .anticon {
color: rgba(255, 255, 255, 0.78);
}
.index-page .action > span:not(.anticon):not(.tg-icon) {
@ -195,16 +54,6 @@ html[data-theme='ultra-dark'] .index-page .action .anticon {
min-width: 0;
}
.index-page .action:hover {
opacity: 0.75;
transform: translateY(-1px);
}
.index-page .ant-card-actions > li {
margin: 8px 0;
min-width: 0;
}
.index-page .action-update {
color: #fa8c16;
font-weight: 600;

View file

@ -11,6 +11,7 @@ import {
Row,
Space,
Spin,
Statistic,
Tag,
Tooltip,
} from 'antd';
@ -39,7 +40,6 @@ import { useTheme } from '@/hooks/useTheme';
import { useStatusQuery } from '@/api/queries/useStatusQuery';
import { useMediaQuery } from '@/hooks/useMediaQuery';
import AppSidebar from '@/components/AppSidebar';
import CustomStatistic from '@/components/CustomStatistic';
import LazyMount from '@/components/LazyMount';
import { setMessageInstance } from '@/utils/messageBus';
import StatusCard from './StatusCard';
@ -285,14 +285,14 @@ export default function IndexPage() {
<Card title={t('pages.index.operationHours')} hoverable>
<Row gutter={isMobile ? [8, 8] : 0}>
<Col span={12}>
<CustomStatistic
<Statistic
title="Xray"
value={TimeFormatter.formatSecond(status.appStats.uptime)}
prefix={<ThunderboltOutlined />}
/>
</Col>
<Col span={12}>
<CustomStatistic
<Statistic
title="OS"
value={TimeFormatter.formatSecond(status.uptime)}
prefix={<DesktopOutlined />}
@ -306,14 +306,14 @@ export default function IndexPage() {
<Card title={t('usage')} hoverable>
<Row gutter={isMobile ? [8, 8] : 0}>
<Col span={12}>
<CustomStatistic
<Statistic
title={t('pages.index.memory')}
value={SizeFormatter.sizeFormat(status.appStats.mem)}
prefix={<DatabaseOutlined />}
/>
</Col>
<Col span={12}>
<CustomStatistic
<Statistic
title={t('pages.index.threads')}
value={status.appStats.threads}
prefix={<ForkOutlined />}
@ -327,7 +327,7 @@ export default function IndexPage() {
<Card title={t('pages.index.overallSpeed')} hoverable>
<Row gutter={isMobile ? [8, 8] : 0}>
<Col span={12}>
<CustomStatistic
<Statistic
title={t('pages.index.upload')}
value={SizeFormatter.sizeFormat(status.netIO.up)}
prefix={<ArrowUpOutlined />}
@ -335,7 +335,7 @@ export default function IndexPage() {
/>
</Col>
<Col span={12}>
<CustomStatistic
<Statistic
title={t('pages.index.download')}
value={SizeFormatter.sizeFormat(status.netIO.down)}
prefix={<ArrowDownOutlined />}
@ -350,14 +350,14 @@ export default function IndexPage() {
<Card title={t('pages.index.totalData')} hoverable>
<Row gutter={isMobile ? [8, 8] : 0}>
<Col span={12}>
<CustomStatistic
<Statistic
title={t('pages.index.sent')}
value={SizeFormatter.sizeFormat(status.netTraffic.sent)}
prefix={<CloudUploadOutlined />}
/>
</Col>
<Col span={12}>
<CustomStatistic
<Statistic
title={t('pages.index.received')}
value={SizeFormatter.sizeFormat(status.netTraffic.recv)}
prefix={<CloudDownloadOutlined />}
@ -392,14 +392,14 @@ export default function IndexPage() {
>
<Row className={showIp ? 'ip-visible' : 'ip-hidden'} gutter={isMobile ? [8, 8] : 0}>
<Col span={isMobile ? 24 : 12}>
<CustomStatistic
<Statistic
title="IPv4"
value={status.publicIP.ipv4}
prefix={<GlobalOutlined />}
/>
</Col>
<Col span={isMobile ? 24 : 12}>
<CustomStatistic
<Statistic
title="IPv6"
value={status.publicIP.ipv6}
prefix={<GlobalOutlined />}
@ -413,14 +413,14 @@ export default function IndexPage() {
<Card title={t('pages.index.connectionCount')} hoverable>
<Row gutter={isMobile ? [8, 8] : 0}>
<Col span={12}>
<CustomStatistic
<Statistic
title="TCP"
value={status.tcpCount}
prefix={<SwapOutlined />}
/>
</Col>
<Col span={12}>
<CustomStatistic
<Statistic
title="UDP"
value={status.udpCount}
prefix={<SwapOutlined />}

View file

@ -12,33 +12,3 @@
.cursor-pointer {
cursor: pointer;
}
.xray-processing-animation .ant-badge-status-dot {
animation: xray-pulse 1.2s linear infinite;
}
.xray-running-animation .ant-badge-status-processing::after {
border-color: #1677ff;
}
.xray-stop-animation .ant-badge-status-processing::after {
border-color: #fa8c16;
}
.xray-error-animation .ant-badge-status-processing::after {
border-color: #f5222d;
}
@keyframes xray-pulse {
0%,
50%,
100% {
transform: scale(1);
opacity: 1;
}
10% {
transform: scale(1.5);
opacity: 0.2;
}
}

View file

@ -28,13 +28,6 @@ const XRAY_STATE_KEYS: Record<string, string> = {
error: 'pages.index.xrayStatusError',
};
function badgeAnimationClass(color: string): string {
if (color === 'green') return 'xray-running-animation';
if (color === 'orange') return 'xray-stop-animation';
if (color === 'red') return 'xray-error-animation';
return 'xray-processing-animation';
}
export default function XrayStatusCard({
status,
isMobile,
@ -65,12 +58,7 @@ export default function XrayStatusCard({
const extra =
status.xray.state !== 'error' ? (
<Badge
status="processing"
className={`xray-processing-animation ${badgeAnimationClass(status.xray.color)}`}
text={stateText}
color={status.xray.color}
/>
<Badge status="processing" text={stateText} color={status.xray.color} />
) : (
<Popover
title={
@ -93,12 +81,7 @@ export default function XrayStatusCard({
</>
}
>
<Badge
status="processing"
text={stateText}
color={status.xray.color}
className="xray-processing-animation xray-error-animation"
/>
<Badge status="processing" text={stateText} color={status.xray.color} />
</Popover>
);

View file

@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Card, Col, ConfigProvider, Layout, Modal, Row, Spin, message } from 'antd';
import { Card, Col, ConfigProvider, Layout, Modal, Row, Spin, Statistic, message } from 'antd';
import {
CheckCircleOutlined,
CloseCircleOutlined,
@ -14,7 +14,6 @@ import { useNodesQuery } from '@/api/queries/useNodesQuery';
import type { NodeRecord } from '@/api/queries/useNodesQuery';
import { useNodeMutations } from '@/api/queries/useNodeMutations';
import AppSidebar from '@/components/AppSidebar';
import CustomStatistic from '@/components/CustomStatistic';
import NodeList from './NodeList';
import NodeFormModal from './NodeFormModal';
import { setMessageInstance } from '@/utils/messageBus';
@ -109,28 +108,28 @@ export default function NodesPage() {
<Card size="small" hoverable className="summary-card">
<Row gutter={[16, isMobile ? 16 : 12]}>
<Col xs={12} sm={12} md={6}>
<CustomStatistic
<Statistic
title={t('pages.nodes.totalNodes')}
value={String(totals.total)}
prefix={<CloudServerOutlined />}
/>
</Col>
<Col xs={12} sm={12} md={6}>
<CustomStatistic
<Statistic
title={t('pages.nodes.onlineNodes')}
value={String(totals.online)}
prefix={<CheckCircleOutlined style={{ color: '#52c41a' }} />}
/>
</Col>
<Col xs={12} sm={12} md={6}>
<CustomStatistic
<Statistic
title={t('pages.nodes.offlineNodes')}
value={String(totals.offline)}
prefix={<CloseCircleOutlined style={{ color: '#ff4d4f' }} />}
/>
</Col>
<Col xs={12} sm={12} md={6}>
<CustomStatistic
<Statistic
title={t('pages.nodes.avgLatency')}
value={totals.avgLatency > 0 ? `${totals.avgLatency} ms` : '-'}
prefix={<ThunderboltOutlined />}

View file

@ -6,7 +6,6 @@
.nodes-page .ant-card,
.api-docs-page .ant-card {
border-radius: 12px;
border: 1px solid rgba(0, 0, 0, 0.06);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
transition: transform 0.2s ease, box-shadow 0.25s ease, border-color 0.2s ease;
}
@ -18,7 +17,6 @@ body.dark .xray-page .ant-card,
body.dark .settings-page .ant-card,
body.dark .nodes-page .ant-card,
body.dark .api-docs-page .ant-card {
border-color: rgba(255, 255, 255, 0.06);
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.03);
@ -31,7 +29,6 @@ html[data-theme='ultra-dark'] .xray-page .ant-card,
html[data-theme='ultra-dark'] .settings-page .ant-card,
html[data-theme='ultra-dark'] .nodes-page .ant-card,
html[data-theme='ultra-dark'] .api-docs-page .ant-card {
border-color: rgba(255, 255, 255, 0.04);
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.6),
inset 0 1px 0 rgba(255, 255, 255, 0.025);
@ -45,7 +42,6 @@ html[data-theme='ultra-dark'] .api-docs-page .ant-card {
.nodes-page .ant-card.ant-card-hoverable:hover,
.api-docs-page .ant-card.ant-card-hoverable:hover {
transform: translateY(-2px);
border-color: rgba(0, 0, 0, 0.10);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
}
@ -56,7 +52,6 @@ body.dark .xray-page .ant-card.ant-card-hoverable:hover,
body.dark .settings-page .ant-card.ant-card-hoverable:hover,
body.dark .nodes-page .ant-card.ant-card-hoverable:hover,
body.dark .api-docs-page .ant-card.ant-card-hoverable:hover {
border-color: rgba(255, 255, 255, 0.12);
box-shadow:
0 8px 24px rgba(0, 0, 0, 0.5),
inset 0 1px 0 rgba(255, 255, 255, 0.04);
@ -69,22 +64,11 @@ html[data-theme='ultra-dark'] .xray-page .ant-card.ant-card-hoverable:hover,
html[data-theme='ultra-dark'] .settings-page .ant-card.ant-card-hoverable:hover,
html[data-theme='ultra-dark'] .nodes-page .ant-card.ant-card-hoverable:hover,
html[data-theme='ultra-dark'] .api-docs-page .ant-card.ant-card-hoverable:hover {
border-color: rgba(255, 255, 255, 0.08);
box-shadow:
0 8px 24px rgba(0, 0, 0, 0.75),
inset 0 1px 0 rgba(255, 255, 255, 0.03);
}
.index-page .ant-card .ant-card-head,
.clients-page .ant-card .ant-card-head,
.inbounds-page .ant-card .ant-card-head,
.xray-page .ant-card .ant-card-head,
.settings-page .ant-card .ant-card-head,
.nodes-page .ant-card .ant-card-head,
.api-docs-page .ant-card .ant-card-head {
border-bottom-color: rgba(0, 0, 0, 0.06);
}
.index-page .ant-card .ant-card-actions,
.clients-page .ant-card .ant-card-actions,
.inbounds-page .ant-card .ant-card-actions,
@ -92,76 +76,5 @@ html[data-theme='ultra-dark'] .api-docs-page .ant-card.ant-card-hoverable:hover
.settings-page .ant-card .ant-card-actions,
.nodes-page .ant-card .ant-card-actions,
.api-docs-page .ant-card .ant-card-actions {
border-top-color: rgba(0, 0, 0, 0.06);
background: transparent;
}
.index-page .ant-card .ant-card-actions > li,
.clients-page .ant-card .ant-card-actions > li,
.inbounds-page .ant-card .ant-card-actions > li,
.xray-page .ant-card .ant-card-actions > li,
.settings-page .ant-card .ant-card-actions > li,
.nodes-page .ant-card .ant-card-actions > li,
.api-docs-page .ant-card .ant-card-actions > li {
border-inline-end-color: rgba(0, 0, 0, 0.06);
}
body.dark .index-page .ant-card .ant-card-head,
body.dark .clients-page .ant-card .ant-card-head,
body.dark .inbounds-page .ant-card .ant-card-head,
body.dark .xray-page .ant-card .ant-card-head,
body.dark .settings-page .ant-card .ant-card-head,
body.dark .nodes-page .ant-card .ant-card-head,
body.dark .api-docs-page .ant-card .ant-card-head {
border-bottom-color: rgba(255, 255, 255, 0.06);
}
body.dark .index-page .ant-card .ant-card-actions,
body.dark .clients-page .ant-card .ant-card-actions,
body.dark .inbounds-page .ant-card .ant-card-actions,
body.dark .xray-page .ant-card .ant-card-actions,
body.dark .settings-page .ant-card .ant-card-actions,
body.dark .nodes-page .ant-card .ant-card-actions,
body.dark .api-docs-page .ant-card .ant-card-actions {
border-top-color: rgba(255, 255, 255, 0.06);
}
body.dark .index-page .ant-card .ant-card-actions > li,
body.dark .clients-page .ant-card .ant-card-actions > li,
body.dark .inbounds-page .ant-card .ant-card-actions > li,
body.dark .xray-page .ant-card .ant-card-actions > li,
body.dark .settings-page .ant-card .ant-card-actions > li,
body.dark .nodes-page .ant-card .ant-card-actions > li,
body.dark .api-docs-page .ant-card .ant-card-actions > li {
border-inline-end-color: rgba(255, 255, 255, 0.06);
}
html[data-theme='ultra-dark'] .index-page .ant-card .ant-card-head,
html[data-theme='ultra-dark'] .clients-page .ant-card .ant-card-head,
html[data-theme='ultra-dark'] .inbounds-page .ant-card .ant-card-head,
html[data-theme='ultra-dark'] .xray-page .ant-card .ant-card-head,
html[data-theme='ultra-dark'] .settings-page .ant-card .ant-card-head,
html[data-theme='ultra-dark'] .nodes-page .ant-card .ant-card-head,
html[data-theme='ultra-dark'] .api-docs-page .ant-card .ant-card-head {
border-bottom-color: rgba(255, 255, 255, 0.04);
}
html[data-theme='ultra-dark'] .index-page .ant-card .ant-card-actions,
html[data-theme='ultra-dark'] .clients-page .ant-card .ant-card-actions,
html[data-theme='ultra-dark'] .inbounds-page .ant-card .ant-card-actions,
html[data-theme='ultra-dark'] .xray-page .ant-card .ant-card-actions,
html[data-theme='ultra-dark'] .settings-page .ant-card .ant-card-actions,
html[data-theme='ultra-dark'] .nodes-page .ant-card .ant-card-actions,
html[data-theme='ultra-dark'] .api-docs-page .ant-card .ant-card-actions {
border-top-color: rgba(255, 255, 255, 0.04);
}
html[data-theme='ultra-dark'] .index-page .ant-card .ant-card-actions > li,
html[data-theme='ultra-dark'] .clients-page .ant-card .ant-card-actions > li,
html[data-theme='ultra-dark'] .inbounds-page .ant-card .ant-card-actions > li,
html[data-theme='ultra-dark'] .xray-page .ant-card .ant-card-actions > li,
html[data-theme='ultra-dark'] .settings-page .ant-card .ant-card-actions > li,
html[data-theme='ultra-dark'] .nodes-page .ant-card .ant-card-actions > li,
html[data-theme='ultra-dark'] .api-docs-page .ant-card .ant-card-actions > li {
border-inline-end-color: rgba(255, 255, 255, 0.04);
}