style(frontend): refined dark/ultra palette + shared pro card frame

- Dark tokens shifted to a cooler, Linear-style palette: page #1a1b1f,
  sidebar/header #15161a (recessed nav, darker than cards), card
  #23252b, elevated #2d2f37
- Ultra dark: page pure #000 for OLED, sidebar #050507 disappears into
  the frame, card #101013 with a clear step, elevated #1a1a1e
- New styles/page-cards.css holds the card border/shadow/hover rules so
  all seven content pages (index, clients, inbounds, xray, settings,
  nodes, api-docs) share one definition instead of duplicating in each
  page CSS
- Dashboard typography: uppercase card titles with letter-spacing,
  larger 17px stat values, subtle gradient divider between stat columns,
  ellipsis on action labels so "Backup & Restore" doesn't break the
  card height at mid widths
- Light --bg-page stays at #e6e8ec for the contrast against white cards
This commit is contained in:
MHSanaei 2026-05-22 12:43:08 +02:00
parent f9fb197cdb
commit 3939356b70
No known key found for this signature in database
GPG key ID: 7E4060F2FBE5AB7A
19 changed files with 382 additions and 57 deletions

View file

@ -1,25 +1,38 @@
.ant-statistic-content {
font-size: 15px !important;
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: 15px !important;
font-size: 17px !important;
}
.ant-statistic-content-prefix {
margin-inline-end: 6px !important;
margin-inline-end: 8px !important;
opacity: 0.7;
}
.ant-statistic-content-prefix .anticon {
font-size: 16px !important;
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: 12px !important;
margin-bottom: 4px !important;
font-size: 11px !important;
margin-bottom: 6px !important;
letter-spacing: 0.6px;
text-transform: uppercase;
opacity: 0.55;
font-weight: 500;
}
body.dark .ant-statistic-content {

View file

@ -29,44 +29,44 @@ const initialUltra = readBool(STORAGE_ULTRA, false);
applyDom(initialDark, initialUltra);
const DARK_TOKENS = {
colorBgBase: '#1e1e1e',
colorBgLayout: '#1e1e1e',
colorBgContainer: '#252526',
colorBgElevated: '#2d2d30',
colorBgBase: '#1a1b1f',
colorBgLayout: '#1a1b1f',
colorBgContainer: '#23252b',
colorBgElevated: '#2d2f37',
};
const ULTRA_DARK_TOKENS = {
colorBgBase: '#000',
colorBgLayout: '#000',
colorBgContainer: '#0a0a0a',
colorBgElevated: '#141414',
colorBgContainer: '#101013',
colorBgElevated: '#1a1a1e',
};
const DARK_LAYOUT_TOKENS = {
bodyBg: '#1e1e1e',
headerBg: '#252526',
bodyBg: '#1a1b1f',
headerBg: '#15161a',
headerColor: '#ffffff',
footerBg: '#1e1e1e',
siderBg: '#252526',
triggerBg: '#333333',
footerBg: '#1a1b1f',
siderBg: '#15161a',
triggerBg: '#23252b',
triggerColor: '#ffffff',
};
const ULTRA_DARK_LAYOUT_TOKENS = {
bodyBg: '#000',
headerBg: '#0a0a0a',
headerBg: '#050507',
headerColor: '#ffffff',
footerBg: '#000',
siderBg: '#0a0a0a',
triggerBg: '#141414',
siderBg: '#050507',
triggerBg: '#1a1a1e',
triggerColor: '#ffffff',
};
const DARK_MENU_TOKENS = {
darkItemBg: '#252526',
darkSubMenuItemBg: '#1e1e1e',
darkPopupBg: '#252526',
darkItemBg: '#15161a',
darkSubMenuItemBg: '#1a1b1f',
darkPopupBg: '#23252b',
};
const ULTRA_DARK_MENU_TOKENS = {
darkItemBg: '#0a0a0a',
darkItemBg: '#050507',
darkSubMenuItemBg: '#000',
darkPopupBg: '#0a0a0a',
darkPopupBg: '#101013',
};
export function buildAntdThemeConfig(isDark: boolean, isUltra: boolean): ThemeConfig {

View file

@ -6,13 +6,13 @@
}
.api-docs-page.is-dark {
--bg-page: #1e1e1e;
--bg-card: #252526;
--bg-page: #1a1b1f;
--bg-card: #23252b;
}
.api-docs-page.is-dark.is-ultra {
--bg-page: #000;
--bg-card: #0a0a0a;
--bg-card: #101013;
}
.api-docs-page .content-shell {

View file

@ -24,6 +24,7 @@ import { sections as allSections } from './endpoints.js';
import EndpointSection from './EndpointSection';
import type { Section } from './EndpointSection';
import CodeBlock from './CodeBlock';
import '@/styles/page-cards.css';
import './ApiDocsPage.css';
const sectionIcons: Record<string, ComponentType<{ className?: string }>> = {

View file

@ -6,13 +6,13 @@
}
.clients-page.is-dark {
--bg-page: #1e1e1e;
--bg-card: #252526;
--bg-page: #1a1b1f;
--bg-card: #23252b;
}
.clients-page.is-dark.is-ultra {
--bg-page: #050505;
--bg-card: #0c0e12;
--bg-page: #000;
--bg-card: #101013;
}
.clients-page .ant-layout,

View file

@ -53,6 +53,7 @@ import ClientFormModal from './ClientFormModal';
import ClientInfoModal from './ClientInfoModal';
import ClientQrModal from './ClientQrModal';
import ClientBulkAddModal from './ClientBulkAddModal';
import '@/styles/page-cards.css';
import './ClientsPage.css';
const basePath = window.X_UI_BASE_PATH || '';

View file

@ -7,13 +7,13 @@
}
.inbounds-page.is-dark {
--bg-page: #1e1e1e;
--bg-card: #252526;
--bg-page: #1a1b1f;
--bg-card: #23252b;
}
.inbounds-page.is-dark.is-ultra {
--bg-page: #050505;
--bg-card: #0c0e12;
--bg-page: #000;
--bg-card: #101013;
}
.inbounds-page .ant-layout,

View file

@ -36,6 +36,7 @@ import InboundList from './InboundList';
import InboundFormModal from './InboundFormModal';
import InboundInfoModal from './InboundInfoModal';
import QrCodeModal from './QrCodeModal';
import '@/styles/page-cards.css';
import './InboundsPage.css';
type RowAction =

View file

@ -7,13 +7,13 @@
}
.index-page.is-dark {
--bg-page: #1e1e1e;
--bg-card: #252526;
--bg-page: #1a1b1f;
--bg-card: #23252b;
}
.index-page.is-dark.is-ultra {
--bg-page: #050505;
--bg-card: #0c0e12;
--bg-page: #000;
--bg-card: #101013;
}
.index-page .ant-layout,
@ -40,9 +40,147 @@
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;
transition: opacity 0.15s ease, transform 0.15s ease;
}
.index-page .action > span:not(.anticon):not(.tg-icon) {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
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 {

View file

@ -51,6 +51,7 @@ import SystemHistoryModal from './SystemHistoryModal';
import XrayMetricsModal from './XrayMetricsModal';
import XrayLogModal from './XrayLogModal';
import VersionModal from './VersionModal';
import '@/styles/page-cards.css';
import './IndexPage.css';
export default function IndexPage() {

View file

@ -43,7 +43,7 @@
.login-app.is-dark.is-ultra {
--bg-page: #000;
--bg-card: rgba(15, 17, 28, 0.6);
--bg-card-solid: #0a0c14;
--bg-card-solid: #101013;
--color-border: rgba(255, 255, 255, 0.06);
--blob-1: rgba(99, 102, 241, 0.30);
--blob-2: rgba(236, 72, 153, 0.22);

View file

@ -6,13 +6,13 @@
}
.nodes-page.is-dark {
--bg-page: #1e1e1e;
--bg-card: #252526;
--bg-page: #1a1b1f;
--bg-card: #23252b;
}
.nodes-page.is-dark.is-ultra {
--bg-page: #050505;
--bg-card: #0c0e12;
--bg-page: #000;
--bg-card: #101013;
}
.nodes-page .ant-layout,

View file

@ -18,6 +18,7 @@ import CustomStatistic from '@/components/CustomStatistic';
import NodeList from './NodeList';
import NodeFormModal from './NodeFormModal';
import { setMessageInstance } from '@/utils/messageBus';
import '@/styles/page-cards.css';
import './NodesPage.css';
const basePath = window.X_UI_BASE_PATH || '';

View file

@ -6,13 +6,13 @@
}
.settings-page.is-dark {
--bg-page: #1e1e1e;
--bg-card: #252526;
--bg-page: #1a1b1f;
--bg-card: #23252b;
}
.settings-page.is-dark.is-ultra {
--bg-page: #050505;
--bg-card: #0c0e12;
--bg-page: #000;
--bg-card: #101013;
}
.settings-page .ant-layout,

View file

@ -35,6 +35,7 @@ import SecurityTab from './SecurityTab';
import TelegramTab from './TelegramTab';
import SubscriptionGeneralTab from './SubscriptionGeneralTab';
import SubscriptionFormatsTab from './SubscriptionFormatsTab';
import '@/styles/page-cards.css';
import './SettingsPage.css';
interface ApiMsg {

View file

@ -6,13 +6,13 @@
}
.subscription-page.is-dark {
--bg-page: #1e1e1e;
--bg-card: #252526;
--bg-page: #1a1b1f;
--bg-card: #23252b;
}
.subscription-page.is-dark.is-ultra {
--bg-page: #050505;
--bg-card: #0c0e12;
--bg-page: #000;
--bg-card: #101013;
}
.subscription-page .ant-layout,

View file

@ -7,13 +7,13 @@
}
.xray-page.is-dark {
--bg-page: #1e1e1e;
--bg-card: #252526;
--bg-page: #1a1b1f;
--bg-card: #23252b;
}
.xray-page.is-dark.is-ultra {
--bg-page: #050505;
--bg-card: #0c0e12;
--bg-page: #000;
--bg-card: #101013;
}
.xray-page .ant-layout,

View file

@ -45,6 +45,7 @@ import BalancersTab from './BalancersTab';
import DnsTab from './DnsTab';
import WarpModal from './WarpModal';
import NordModal from './NordModal';
import '@/styles/page-cards.css';
import './XrayPage.css';
const TAB_KEYS = ['tpl-basic', 'tpl-routing', 'tpl-outbound', 'tpl-balancer', 'tpl-dns', 'tpl-advanced'];

View file

@ -0,0 +1,167 @@
.index-page .ant-card,
.clients-page .ant-card,
.inbounds-page .ant-card,
.xray-page .ant-card,
.settings-page .ant-card,
.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;
}
body.dark .index-page .ant-card,
body.dark .clients-page .ant-card,
body.dark .inbounds-page .ant-card,
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);
}
html[data-theme='ultra-dark'] .index-page .ant-card,
html[data-theme='ultra-dark'] .clients-page .ant-card,
html[data-theme='ultra-dark'] .inbounds-page .ant-card,
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);
}
.index-page .ant-card.ant-card-hoverable:hover,
.clients-page .ant-card.ant-card-hoverable:hover,
.inbounds-page .ant-card.ant-card-hoverable:hover,
.xray-page .ant-card.ant-card-hoverable:hover,
.settings-page .ant-card.ant-card-hoverable:hover,
.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);
}
body.dark .index-page .ant-card.ant-card-hoverable:hover,
body.dark .clients-page .ant-card.ant-card-hoverable:hover,
body.dark .inbounds-page .ant-card.ant-card-hoverable:hover,
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);
}
html[data-theme='ultra-dark'] .index-page .ant-card.ant-card-hoverable:hover,
html[data-theme='ultra-dark'] .clients-page .ant-card.ant-card-hoverable:hover,
html[data-theme='ultra-dark'] .inbounds-page .ant-card.ant-card-hoverable:hover,
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,
.xray-page .ant-card .ant-card-actions,
.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);
}