feat: M3-M7 全模块落地 + 集成接线

- M3 认证/权限/审计/限流 + 全部 REST 接口
- M4 管理面板(MIUIx + 成绩录入/积分榜下钻/大屏控制)
- M5a 大屏页面(六页 + 明细覆盖层 + 缩放适配)
- M6 WebSocket 实时同步中心
- M7 xlsx 导出 + 赛事包 zip + 备份
- 规则包 YAML 导入导出(差异预览)
- 字体 vendor 子集化(4 套 x2 字重)
- 集成:realtime/export/rules 接入 main+httpapi;backup 子命令
- 修复:中间件包装 ResponseWriter 丢失 Hijacker,导致 WS 握手 501
This commit is contained in:
AskaEth committed 2026-09-09 19:02:24 +08:00
1 parent d0e3b93e76
commit bc8b1ee6c0
145 files changed
+25159 -321

No files matched your search

+90
View File
@@ -0,0 +1,90 @@
# vendor/fonts —— 本地字体(离线部署,禁止 CDN)
现场部署无外网,所有中文字体必须随仓库分发、从本目录本地加载。本目录只放**子集化后的 woff2**,
原始大字体(每套 8–12 MB)不入库。
## 字体清单
| id | 显示名 | font-family | 字重 | 单套合计 |
| --- | --- | --- | --- | --- |
| `noto-sans` | Noto Sans SC | `Noto Sans SC` | 400 / 700 | 2.72 MB |
| `noto-serif` | Noto Serif SC | `Noto Serif SC` | 400 / 700 | 3.71 MB |
| `misans` | MiSans | `MiSans` | 400 / 700 | 1.76 MB |
| `source-han-sans` | 思源黑体 Source Han Sans CN | `Source Han Sans CN` | 400 / 700 | 2.72 MB |
合计 8 个 woff2、10.91 MB。逐文件体积与 sha256 见 [`manifest.json`](./manifest.json),
每个文件均 ≤ 4 MB / 套的目标(最大为 Noto Serif SC 的 3.71 MB)。
## 来源与授权
| 字体 | 来源 | 授权 |
| --- | --- | --- |
| Noto Sans SC | Google Noto(`~/Fonts/noto-sans/sc`) | SIL Open Font License 1.1 |
| Noto Serif SC | Google Noto(`~/Fonts/noto-serif/sc/SubsetOTF/SC`) | SIL Open Font License 1.1 |
| MiSans | 小米(`~/Fonts/MiSans/ttf`) | MiSans 字体知识产权许可协议 |
| 思源黑体 Source Han Sans CN | Adobe(`~/Fonts/思源黑体`) | SIL Open Font License 1.1 |
> **授权说明**:本项目为**内部赛事**使用,不对外公开发布,字体授权不构成障碍
> (见 `devdocs/06-大屏端设计.md` 9.1、`devdocs/07-管理面板设计.md` 9)。
> 若将来对外分发,需逐字体复核许可:Noto / 思源黑体为 OFL,可自由分发;
> **MiSans 为厂商许可,公开分发前必须另行确认**。OFL 字体保留其原始版权声明,
> 本目录未附 LICENSE 副本,需要时从 `~/Fonts/<字体目录>/LICENSE` 取回。
## 子集化命令
依赖 `fonttools` + `brotli`(生成 woff2 必需):
```bash
pip install fonttools brotli
# 系统 Python 受 PEP 668 保护时,用带系统包的虚拟环境:
python3 -m venv --system-site-packages ~/.tmp/fontenv
~/.tmp/fontenv/bin/pip install brotli
PYTHON=~/.tmp/fontenv/bin/python vendor/fonts/tools/subset-fonts.sh
```
脚本会:生成字符集 → 逐字体跑 `fontTools.subset` → 重新生成 `manifest.json`。
**子集字符集**:ASCII 可打印字符 + GB2312 全部合法码位(一级 3755 + 二级 3008 汉字,
含全角标点、希腊/俄文、全角数字)+ 常用中文标点与数字符号,共 **7553 字符**。
**子集化参数**:
```bash
python -m fontTools.subset <源字体> \
--flavor=woff2 \
--layout-features='kern,liga,clig,calt,ccmp,locl,mark,mkmk' \
--name-IDs='*' --name-languages='*' \
--recommended-glyphs --drop-tables+=DSIG \
--text="$(字符集)" --output-file=<id>-<weight>.woff2
```
去掉 CJK 竖排(`vert`/`vrt2`)等用不到的 OpenType 特性以压体积;保留 `kern`/`liga` 等排版特性。
## 如何使用
`web/src/shared/styles/fonts.css` 为每套字体写好 `@font-face`(`font-display: swap`)
与 `--font-<id>` 变量。在全局样式入口加一行即可生效:
```css
@import './fonts.css';
```
CSS 里的 `url()` 是相对 `web/src/shared/styles/` 的 `../../../../vendor/fonts/...`。
Vite 构建时会把这些 woff2 作为资源打进产物(输出到 `server/internal/webui/assets/`),
运行时不再依赖仓库外的路径。
## 如何新增字体
1. 把源字体(`.otf`/`.ttf`)放到 `~/Fonts/<目录>/`(不要直接复制进仓库)。
2. 在 `tools/fonts.conf` 追加一行:`id|显示名|font-family|分类|授权|来源说明|Regular 源文件|Bold 源文件`。
3. 重跑 `PYTHON=... vendor/fonts/tools/subset-fonts.sh`,它会生成 `<id>-regular.woff2`、
`<id>-bold.woff2` 并更新 `manifest.json`。
4. 在 `web/src/shared/styles/fonts.css` 补对应 `@font-face` 和 `--font-<id>` 变量。
5. 检查单套合计 ≤ 4 MB;超了就精简字符集或换更轻的字重。
## 已知限制
- MiSans 缺 `・`(U+30FB,日文片假名中点,GB2312 内含),其余 7552 个字符齐全;
中文场景无影响。其余三套字符集全覆盖。
- 只做 Regular + Bold 两档;如需 Medium/Light,按上节流程加字重(需扩展 `fonts.conf` 的字段格式)。
- 字体文件已子集化,**不要**用原始 `~/Fonts` 大字体覆盖本目录文件。
+139
View File
@@ -0,0 +1,139 @@
{
"version": 1,
"generatedAt": "2026-09-09",
"generator": "vendor/fonts/tools/subset-fonts.sh",
"subset": {
"charset": "ASCII + GB2312 一级/二级汉字 + 中文标点 + 数字符号",
"charCount": 7553,
"format": "woff2",
"layoutFeatures": "kern,liga,clig,calt,ccmp,locl,mark,mkmk"
},
"totalBytes": 11434920,
"totalSize": "10.91 MB",
"fonts": [
{
"id": "noto-sans",
"displayName": "Noto Sans SC",
"fontFamily": "Noto Sans SC",
"cssVar": "--font-noto-sans",
"category": "sans-serif",
"license": "OFL-1.1",
"source": "Google Noto Sans SC (noto-sans/sc)",
"totalBytes": 2849388,
"totalSize": "2.72 MB",
"weights": [
{
"weight": 400,
"style": "normal",
"file": "noto-sans-regular.woff2",
"path": "vendor/fonts/noto-sans-regular.woff2",
"bytes": 1399176,
"size": "1.33 MB",
"sha256": "a126c29405324acddf2cf3d47ba76762f1fa39cd7b1c0be6dc98f4fd26cfafb8"
},
{
"weight": 700,
"style": "normal",
"file": "noto-sans-bold.woff2",
"path": "vendor/fonts/noto-sans-bold.woff2",
"bytes": 1450212,
"size": "1.38 MB",
"sha256": "206e958255a9e961243fbbd44c52ba05fb166e452de315fa3967e3c78bb8d499"
}
]
},
{
"id": "noto-serif",
"displayName": "Noto Serif SC",
"fontFamily": "Noto Serif SC",
"cssVar": "--font-noto-serif",
"category": "serif",
"license": "OFL-1.1",
"source": "Google Noto Serif SC (noto-serif/sc/SubsetOTF/SC)",
"totalBytes": 3887184,
"totalSize": "3.71 MB",
"weights": [
{
"weight": 400,
"style": "normal",
"file": "noto-serif-regular.woff2",
"path": "vendor/fonts/noto-serif-regular.woff2",
"bytes": 1892872,
"size": "1.81 MB",
"sha256": "666a88ddc3f50f8ae999991d9ebd5eeb58ad267f6f9b38b75a580599e0fe72c2"
},
{
"weight": 700,
"style": "normal",
"file": "noto-serif-bold.woff2",
"path": "vendor/fonts/noto-serif-bold.woff2",
"bytes": 1994312,
"size": "1.90 MB",
"sha256": "7a8fc67787af8d4a8ff88b63f672b888ff29746ad08af0c4b4b31273d15ffce3"
}
]
},
{
"id": "misans",
"displayName": "MiSans",
"fontFamily": "MiSans",
"cssVar": "--font-misans",
"category": "sans-serif",
"license": "MiSans Font License",
"source": "小米 MiSans (MiSans/ttf)",
"totalBytes": 1841888,
"totalSize": "1.76 MB",
"weights": [
{
"weight": 400,
"style": "normal",
"file": "misans-regular.woff2",
"path": "vendor/fonts/misans-regular.woff2",
"bytes": 910956,
"size": "890 KB",
"sha256": "ca7c73c33dde913833012f16334e59d8f6a3cdea856e537a52ff27b95ab01493"
},
{
"weight": 700,
"style": "normal",
"file": "misans-bold.woff2",
"path": "vendor/fonts/misans-bold.woff2",
"bytes": 930932,
"size": "909 KB",
"sha256": "18119cd03ed6931c339d8f7cfc4bcf2c7258ee6a9e2191b43d75dcc26ef64f41"
}
]
},
{
"id": "source-han-sans",
"displayName": "思源黑体 Source Han Sans CN",
"fontFamily": "Source Han Sans CN",
"cssVar": "--font-source-han-sans",
"category": "sans-serif",
"license": "OFL-1.1",
"source": "Adobe Source Han Sans CN (思源黑体)",
"totalBytes": 2856460,
"totalSize": "2.72 MB",
"weights": [
{
"weight": 400,
"style": "normal",
"file": "source-han-sans-regular.woff2",
"path": "vendor/fonts/source-han-sans-regular.woff2",
"bytes": 1399540,
"size": "1.33 MB",
"sha256": "f85482af4eb07d66df271e475f00484f764326c1cb4fb9b65c90589bc4588875"
},
{
"weight": 700,
"style": "normal",
"file": "source-han-sans-bold.woff2",
"path": "vendor/fonts/source-han-sans-bold.woff2",
"bytes": 1456920,
"size": "1.39 MB",
"sha256": "3e2e3f54161b8a96c59001d40855835539ef97f6c80c58cdb93fcc4f2baae4dd"
}
]
}
]
}
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+12
View File
@@ -0,0 +1,12 @@
# 字体清单(subset-fonts.sh 与 gen-manifest.py 的唯一数据源)
#
# 字段(| 分隔):
# id | 显示名 | font-family | 分类 | 授权 | 来源说明 | Regular 源文件 | Bold 源文件
#
# 源文件相对 $FONT_SRC(默认 ~/Fonts)。
# 新增字体:在下面加一行,然后重跑
# PYTHON=<带 fonttools+brotli 的 python> vendor/fonts/tools/subset-fonts.sh
noto-sans|Noto Sans SC|Noto Sans SC|sans-serif|OFL-1.1|Google Noto Sans SC (noto-sans/sc)|noto-sans/sc/NotoSansSC-Regular.otf|noto-sans/sc/NotoSansSC-Bold.otf
noto-serif|Noto Serif SC|Noto Serif SC|serif|OFL-1.1|Google Noto Serif SC (noto-serif/sc/SubsetOTF/SC)|noto-serif/sc/SubsetOTF/SC/NotoSerifSC-Regular.otf|noto-serif/sc/SubsetOTF/SC/NotoSerifSC-Bold.otf
misans|MiSans|MiSans|sans-serif|MiSans Font License|小米 MiSans (MiSans/ttf)|MiSans/ttf/MiSans-Regular.ttf|MiSans/ttf/MiSans-Bold.ttf
source-han-sans|思源黑体 Source Han Sans CN|Source Han Sans CN|sans-serif|OFL-1.1|Adobe Source Han Sans CN (思源黑体)|思源黑体/SourceHanSansCN-Regular.otf|思源黑体/SourceHanSansCN-Bold.otf
+132
View File
@@ -0,0 +1,132 @@
#!/usr/bin/env python3
"""生成 vendor/fonts/manifest.json。
数据源:vendor/fonts/tools/fonts.conf(字体元数据)+ vendor/fonts/*.woff2(实际产物)。
每次跑完 subset-fonts.sh 会自动调用;也可单独运行:
python3 vendor/fonts/tools/gen-manifest.py
"""
from __future__ import annotations
import hashlib
import json
import subprocess
import sys
from datetime import date
from pathlib import Path
HERE = Path(__file__).resolve().parent
OUT_DIR = HERE.parent
ROOT = OUT_DIR.parent.parent # 仓库根,用于生成 vendor/fonts/... 形式的相对路径
CONF = HERE / "fonts.conf"
WEIGHTS = (("regular", 400), ("bold", 700))
SUBSET_DESC = "ASCII + GB2312 一级/二级汉字 + 中文标点 + 数字符号"
def charset_count() -> int:
"""复用 subset-fonts.sh 的字符集生成逻辑,保证清单与产物一致。"""
code = (
"chars=set()\n"
"for c in range(0x20,0x7F): chars.add(chr(c))\n"
"for hi in range(0xA1,0xFF):\n"
" for lo in range(0xA1,0xFF):\n"
" try: chars.add(bytes((hi,lo)).decode('gb2312'))\n"
" except UnicodeDecodeError: pass\n"
"chars.update(' ·—…‘’“”※→←↑↓√×÷±≈≠≤≥∞°℃℉¥€£©®™№§¶†‡•‰′″〇')\n"
"chars.update('「」『』【】〔〕〖〗《》〈〉')\n"
"chars.update('0123456789')\n"
"chars.update('!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~')\n"
"print(len(chars))\n"
)
return int(subprocess.run([sys.executable, "-c", code], capture_output=True, text=True, check=True).stdout)
def human(n: int) -> str:
return f"{n / 1024 / 1024:.2f} MB" if n >= 1024 * 1024 else f"{n / 1024:.0f} KB"
def read_conf() -> list[dict[str, str]]:
rows = []
for line in CONF.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
fid, name, family, category, license_, source, reg, bold = (p.strip() for p in line.split("|"))
rows.append(
{
"id": fid,
"displayName": name,
"fontFamily": family,
"category": category,
"license": license_,
"source": source,
"regular": reg,
"bold": bold,
}
)
return rows
def main() -> int:
fonts = []
grand_total = 0
for row in read_conf():
weights = []
font_total = 0
for weight_name, weight_class in WEIGHTS:
path = OUT_DIR / f"{row['id']}-{weight_name}.woff2"
if not path.is_file():
print(f"缺少产物: {path},请先运行 subset-fonts.sh", file=sys.stderr)
return 1
data = path.read_bytes()
size = len(data)
font_total += size
weights.append(
{
"weight": weight_class,
"style": "normal",
"file": path.name,
"path": str(path.relative_to(ROOT)),
"bytes": size,
"size": human(size),
"sha256": hashlib.sha256(data).hexdigest(),
}
)
grand_total += font_total
fonts.append(
{
"id": row["id"],
"displayName": row["displayName"],
"fontFamily": row["fontFamily"],
"cssVar": f"--font-{row['id']}",
"category": row["category"],
"license": row["license"],
"source": row["source"],
"totalBytes": font_total,
"totalSize": human(font_total),
"weights": weights,
}
)
manifest = {
"version": 1,
"generatedAt": date.today().isoformat(),
"generator": "vendor/fonts/tools/subset-fonts.sh",
"subset": {
"charset": SUBSET_DESC,
"charCount": charset_count(),
"format": "woff2",
"layoutFeatures": "kern,liga,clig,calt,ccmp,locl,mark,mkmk",
},
"totalBytes": grand_total,
"totalSize": human(grand_total),
"fonts": fonts,
}
out = OUT_DIR / "manifest.json"
out.write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(f"manifest.json: {len(fonts)} 套字体,{len(fonts) * 2} 个文件,合计 {human(grand_total)}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+81
View File
@@ -0,0 +1,81 @@
#!/usr/bin/env python3
"""把子集字体内部的 name 表 / OS2 字重统一成 CSS 期望的规范值。
为什么需要:思源黑体的老版本 OTF 把每个字重写成独立族名
(`Source Han Sans CN Regular` / `Source Han Sans CN Bold`),
MiSans 的 usWeightClass 是 330/630 这类非标准值。
@font-face 的 `font-family` 描述符虽然能屏蔽内部族名,但保持一致更省心,
也避免字体被单独安装/引用时族名对不上。
用法(由 subset-fonts.sh 调用):
python3 vendor/fonts/tools/normalize-names.py
"""
from __future__ import annotations
import sys
from pathlib import Path
from fontTools.ttLib import TTFont
HERE = Path(__file__).resolve().parent
OUT_DIR = HERE.parent
CONF = HERE / "fonts.conf"
WEIGHTS = (("regular", "Regular", 400), ("bold", "Bold", 700))
STALE_NAME_IDS = (1, 2, 3, 4, 6, 16, 17, 21, 22)
PLATFORMS = ((3, 1, 0x409), (1, 0, 0)) # Windows / Mac,英文
def set_name(name, name_id: int, value: str) -> None:
for platform_id, plat_enc_id, lang_id in PLATFORMS:
name.setName(value, name_id, platform_id, plat_enc_id, lang_id)
def normalize(path: Path, family: str, style: str, weight: int) -> None:
font = TTFont(path)
name = font["name"]
for record in list(name.names):
if record.nameID in STALE_NAME_IDS:
name.removeNames(record.nameID, record.platformID, record.platEncID, record.langID)
full_name = family if style == "Regular" else f"{family} {style}"
set_name(name, 1, family) # family
set_name(name, 2, style) # subfamily
set_name(name, 3, f"{full_name}:subset") # unique id
set_name(name, 4, full_name) # full name
set_name(name, 6, full_name.replace(" ", "")) # postscript name
set_name(name, 16, family) # typographic family
set_name(name, 17, style) # typographic subfamily
os2 = font["OS/2"]
os2.usWeightClass = weight
if weight >= 700:
os2.fsSelection = (os2.fsSelection & ~0x40) | 0x20 # BOLD
font["head"].macStyle |= 0x01
else:
os2.fsSelection = (os2.fsSelection & ~0x20) | 0x40 # REGULAR
font["head"].macStyle &= ~0x01
font.flavor = "woff2"
font.save(path)
font.close()
def main() -> int:
for line in CONF.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
fid, _, family = [p.strip() for p in line.split("|")][:3]
for weight_name, style, weight_class in WEIGHTS:
path = OUT_DIR / f"{fid}-{weight_name}.woff2"
if not path.is_file():
print(f"缺少产物: {path}", file=sys.stderr)
return 1
normalize(path, family, style, weight_class)
print(f" {path.name:32s} -> {family} / {style} ({weight_class})")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/env bash
# 字体子集化脚本 —— 把 ~/Fonts 下的中文字体裁剪成 woff2 并写入 vendor/fonts/
#
# 用途:离线部署,字体必须随仓库分发,禁止 CDN。
# 产物:vendor/fonts/<id>-<weight>.woff2 + vendor/fonts/manifest.json
#
# 依赖:python3 + fonttools + brotli
# pip install fonttools brotli
# # 系统 Python 受 PEP 668 保护时用虚拟环境:
# # python3 -m venv --system-site-packages ~/.tmp/fontenv
# # ~/.tmp/fontenv/bin/pip install brotli
# # PYTHON=~/.tmp/fontenv/bin/python vendor/fonts/tools/subset-fonts.sh
#
# 用法:
# vendor/fonts/tools/subset-fonts.sh
# FONT_SRC=~/Fonts PYTHON=python3 vendor/fonts/tools/subset-fonts.sh
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OUT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
FONT_SRC="${FONT_SRC:-$HOME/Fonts}"
PYTHON="${PYTHON:-python3}"
FONTS_CONF="$SCRIPT_DIR/fonts.conf"
# 子集字符集:ASCII + GB2312 一级/二级汉字 + 中文标点 + 数字/全角符号
CHARSET="$("$PYTHON" - <<'PY'
import sys
chars = set()
# 1) ASCII 可打印字符
for c in range(0x20, 0x7F):
chars.add(chr(c))
# 2) GB2312 全部合法双字节码位(一级 3755 + 二级 3008 汉字,及全角标点/希腊/俄文等)
for hi in range(0xA1, 0xFF):
for lo in range(0xA1, 0xFF):
try:
chars.add(bytes((hi, lo)).decode("gb2312"))
except UnicodeDecodeError:
pass
# 3) 常用中文标点 / 数字符号补充
chars.update(
" ·—…‘’“”※→←↑↓√×÷±≈≠≤≥∞°℃℉¥€£©®™№§¶†‡•‰′″〇"
"「」『』【】〔〕〖〗《》〈〉"
"0123456789"
"!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"
)
sys.stdout.write("".join(sorted(chars)))
PY
)"
CHAR_COUNT="$(printf '%s' "$CHARSET" | "$PYTHON" -c 'import sys; print(len(sys.stdin.read()))')"
# 子集化参数:
# --flavor=woff2 输出 woff2(需 brotli)
# --layout-features 保留字距/连字等,丢弃 CJK 竖排等无用特性以压体积
# --name-IDs='*' 保留完整 name 表(含本地化族名)
# --recommended-glyphs 保留 .notdef/.null 等推荐字形
# --drop-tables+=DSIG 丢弃无用的数字签名表
SUBSET_OPTS=(
--flavor=woff2
--layout-features='kern,liga,clig,calt,ccmp,locl,mark,mkmk'
--name-IDs='*'
--name-languages='*'
--recommended-glyphs
--drop-tables+=DSIG
)
mkdir -p "$OUT_DIR"
echo "charset: ${CHAR_COUNT} 个字符(ASCII + GB2312 一级/二级 + 中文标点 + 数字符号)"
while IFS='|' read -r id name family category license source_note reg_src bold_src; do
case "$id" in ''|'#'*) continue ;; esac
for pair in "regular:400:$reg_src" "bold:700:$bold_src"; do
IFS=':' read -r weight_name weight_class src_rel <<<"$pair"
src="$FONT_SRC/$src_rel"
out="$OUT_DIR/$id-$weight_name.woff2"
[ -f "$src" ] || { echo "缺少源字体: $src" >&2; exit 1; }
printf ' %-26s %s -> %s\n' "$id-$weight_name" "$(basename "$src")" "$(basename "$out")"
"$PYTHON" -m fontTools.subset "$src" "${SUBSET_OPTS[@]}" \
--text="$CHARSET" --output-file="$out"
done
done < "$FONTS_CONF"
"$PYTHON" "$SCRIPT_DIR/normalize-names.py"
"$PYTHON" "$SCRIPT_DIR/gen-manifest.py"
echo "完成。产物目录:$OUT_DIR"
ls -la "$OUT_DIR"/*.woff2
+81
View File
@@ -0,0 +1,81 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Accordion / Collapse Component
═══════════════════════════════════════════ */
.mx-accordion{
display:flex;flex-direction:column;
border:1px solid var(--mx-divider);
border-radius:var(--mx-r);
overflow:hidden;
}
/* Item */
.mx-accordion-item{
border-bottom:1px solid var(--mx-divider);
}
.mx-accordion-item:last-child{border-bottom:none}
/* Header / Trigger */
.mx-accordion-header{
display:flex;align-items:center;gap:10px;
padding:14px 16px;
cursor:pointer;
font-size:14px;font-weight:600;
color:var(--mx-text);
transition:all var(--mx-d1) var(--mx-ease);
-webkit-tap-highlight-color:transparent;
}
.mx-accordion-header:hover{background:var(--mx-s-hover)}
.mx-accordion-header:active{background:var(--mx-s-active)}
/* Icon slot */
.mx-accordion-header svg{flex-shrink:0;color:var(--mx-t2)}
.mx-accordion-header .mx-accordion-title{flex:1}
/* Arrow */
.mx-accordion-arrow{
color:var(--mx-t3);flex-shrink:0;
transition:transform var(--mx-d2) var(--mx-ease);
}
.mx-accordion-item.open .mx-accordion-arrow{transform:rotate(180deg)}
/* Content */
.mx-accordion-content{
max-height:0;overflow:hidden;
transition:max-height var(--mx-d3) var(--mx-ease);
}
.mx-accordion-item.open .mx-accordion-content{
max-height:500px;
}
.mx-accordion-body{
padding:0 16px 16px;
font-size:13px;line-height:1.6;
color:var(--mx-text-on-surface);
}
/* Card variant — separate cards */
.mx-accordion-card{border:none;gap:8px;background:none}
.mx-accordion-card .mx-accordion-item{
background:var(--mx-s2);
border:1px solid var(--mx-divider);
border-radius:var(--mx-r);
overflow:hidden;
}
.mx-accordion-card .mx-accordion-item:last-child{border-bottom:1px solid var(--mx-divider)}
/* Flush variant — no borders */
.mx-accordion-flush{border:none;border-radius:0}
.mx-accordion-flush .mx-accordion-item{border:none}
.mx-accordion-flush .mx-accordion-header{padding:12px 0}
/* Disabled */
.mx-accordion-header.disabled{
opacity:0.4;cursor:not-allowed;pointer-events:none;
}
/* Mobile */
@media(max-width:767px){
.mx-accordion-header{padding:14px 14px;min-height:48px}
.mx-accordion-body{font-size:14px}
.mx-accordion-card .mx-accordion-item{border-radius:var(--mx-r-sm)}
}
+53
View File
@@ -0,0 +1,53 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Activity List Component
═══════════════════════════════════════════ */
.mx-activity-item{
display:flex;align-items:flex-start;gap:12px;
padding:13px 0;
border-bottom:1px solid var(--mx-divider);
transition:all var(--mx-d1);
}
.mx-activity-item:last-child{border-bottom:none}
.mx-activity-item:active{
background:var(--mx-s-active);
margin:0 -16px;padding:13px 16px;border-radius:var(--mx-r-sm);
}
.mx-activity-dot{
width:8px;height:8px;border-radius:50%;
margin-top:6px;flex-shrink:0;
transition:transform var(--mx-d2) var(--mx-spring);
}
.mx-activity-item:hover .mx-activity-dot{transform:scale(1.5)}
.mx-activity-text{font-size:13px;line-height:1.5}
.mx-activity-text b{font-weight:600;color:var(--mx-text)}
.mx-activity-time{font-size:11px;color:var(--mx-t3);margin-top:3px}
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Chart Bar Component
═══════════════════════════════════════════ */
.mx-chart{display:flex;align-items:flex-end;gap:5px;height:160px}
.mx-chart-bar-col{
flex:1;display:flex;flex-direction:column;
align-items:center;gap:6px;min-width:0;
}
.mx-chart-bar{
width:100%;border-radius:7px 7px 3px 3px;
background:var(--mx-primary);
transform-origin:bottom;
transition:filter var(--mx-d1),transform var(--mx-d1);
}
.mx-chart-bar.alt{background:var(--mx-s4)}
.mx-chart-bar:active{filter:brightness(1.2);transform:scaleY(1.04)}
.mx-chart-label{
font-size:10px;color:var(--mx-t3);
font-variant-numeric:tabular-nums;
}
/* Responsive chart height */
@media(min-width:768px){.mx-chart{height:180px}}
@media(min-width:1024px){.mx-chart{height:200px}}
+70
View File
@@ -0,0 +1,70 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Avatar Component
═══════════════════════════════════════════ */
/* Base avatar (extend existing .mx-avatar) */
.mx-avatar-img{
width:32px;height:32px;border-radius:50%;
object-fit:cover;flex-shrink:0;
border:2px solid var(--mx-s2);
}
/* Sizes */
.mx-avatar-xs{width:24px;height:24px;font-size:10px}
.mx-avatar-sm{width:28px;height:28px;font-size:11px}
.mx-avatar-md{width:36px;height:36px;font-size:14px}
.mx-avatar-lg{width:44px;height:44px;font-size:16px}
.mx-avatar-xl{width:56px;height:56px;font-size:20px}
/* Avatar Group */
.mx-avatar-group{
display:flex;
}
.mx-avatar-group .mx-avatar,
.mx-avatar-group .mx-avatar-img{
margin-left:-8px;
border:2px solid var(--mx-s2);
transition:transform var(--mx-d2) var(--mx-spring);
}
.mx-avatar-group .mx-avatar:first-child,
.mx-avatar-group .mx-avatar-img:first-child{margin-left:0}
.mx-avatar-group .mx-avatar:hover,
.mx-avatar-group .mx-avatar-img:hover{
transform:translateY(-2px);
z-index:1;
}
/* Overflow counter */
.mx-avatar-overflow{
width:32px;height:32px;
border-radius:50%;
background:var(--mx-s3);
border:2px solid var(--mx-s2);
display:flex;align-items:center;justify-content:center;
font-size:11px;font-weight:700;
color:var(--mx-t2);
margin-left:-8px;
}
/* Status dot */
.mx-avatar-status{
position:relative;
}
.mx-avatar-status::after{
content:'';position:absolute;
bottom:0;right:0;
width:10px;height:10px;
border-radius:50%;
border:2px solid var(--mx-s2);
}
.mx-avatar-status.online::after{background:var(--mx-green)}
.mx-avatar-status.busy::after{background:var(--mx-red)}
.mx-avatar-status.away::after{background:var(--mx-amber)}
.mx-avatar-status.offline::after{background:var(--mx-t3)}
/* Mobile */
@media(max-width:767px){
.mx-avatar-group .mx-avatar,
.mx-avatar-group .mx-avatar-img{margin-left:-10px}
.mx-avatar-overflow{width:28px;height:28px;font-size:10px}
}
+61
View File
@@ -0,0 +1,61 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Badge & Tag Component
Miuix capsule shape badges
═══════════════════════════════════════════ */
.mx-badge{
display:inline-flex;align-items:center;gap:5px;
padding:3px 10px;
border-radius:var(--mx-r-pill);
font-size:12px;font-weight:600;
transition:filter var(--mx-d1);
}
.mx-badge:hover{filter:brightness(1.15)}
.mx-badge::before{
content:'';width:6px;height:6px;
border-radius:50%;flex-shrink:0;
}
/* Variants */
.mx-badge-success{background:rgba(52,211,153,0.1);color:var(--mx-green)}
.mx-badge-success::before{background:var(--mx-green);animation:mx-pulse 2s infinite}
.mx-badge-warning{background:rgba(251,191,36,0.1);color:var(--mx-amber)}
.mx-badge-warning::before{background:var(--mx-amber)}
.mx-badge-error{background:rgba(248,113,113,0.1);color:var(--mx-red)}
.mx-badge-error::before{background:var(--mx-red);animation:mx-pulse 1.5s infinite}
.mx-badge-info{background:rgba(96,165,250,0.1);color:var(--mx-blue)}
.mx-badge-info::before{background:var(--mx-blue)}
.mx-badge-neutral{background:rgba(139,149,165,0.1);color:var(--mx-t2)}
.mx-badge-neutral::before{background:var(--mx-t2)}
/* Dot-only badge */
.mx-dot{
width:8px;height:8px;
border-radius:50%;
flex-shrink:0;
}
/* Count badge */
.mx-count{
display:inline-flex;align-items:center;justify-content:center;
min-width:18px;height:18px;padding:0 5px;
border-radius:var(--mx-r-pill);
font-size:10px;font-weight:700;
background:var(--mx-primary);color:#fff;
}
@keyframes mx-pulse{
0%,100%{opacity:1;transform:scale(1)}
50%{opacity:0.5;transform:scale(1.3)}
}
/* Mobile */
@media(max-width:767px){
.mx-badge{padding:4px 12px;font-size:13px}
.mx-count{min-width:20px;height:20px;font-size:11px}
}
+89
View File
@@ -0,0 +1,89 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Button Component
═══════════════════════════════════════════ */
.mx-btn{
display:inline-flex;align-items:center;justify-content:center;gap:6px;
height:36px;padding:0 18px;
border-radius:var(--mx-r-sm);
font-size:13px;font-weight:600;
cursor:pointer;
transition:all var(--mx-d1) var(--mx-ease);
-webkit-tap-highlight-color:transparent;
position:relative;overflow:hidden;
white-space:nowrap;
}
.mx-btn:active{transform:scale(0.95);box-shadow:inset 0 2px 4px rgba(0,0,0,0.2)}
/* Primary */
.mx-btn-primary{
background:var(--mx-primary);color:#fff;
box-shadow:0 2px 8px rgba(76,139,245,0.2);
}
.mx-btn-primary:hover{
background:var(--mx-primary-hover);
box-shadow:0 4px 14px rgba(76,139,245,0.3);
transform:translateY(-1px);
}
.mx-btn-primary:active{transform:scale(0.95) translateY(0)}
/* Secondary */
.mx-btn-secondary{
background:var(--mx-s3);color:var(--mx-text);
border:1px solid var(--mx-border);
}
.mx-btn-secondary:hover{
background:var(--mx-s4);
border-color:var(--mx-t3);
}
/* Ghost */
.mx-btn-ghost{
background:transparent;color:var(--mx-t2);
}
.mx-btn-ghost:hover{
background:var(--mx-s-hover);color:var(--mx-text);
}
/* Danger */
.mx-btn-danger{
background:var(--mx-red);color:#fff;
}
.mx-btn-danger:hover{
filter:brightness(1.1);
box-shadow:0 4px 14px rgba(248,113,113,0.25);
}
/* Pill — Miuix style capsule button */
.mx-btn-pill{
border-radius:var(--mx-r-pill);
height:32px;padding:0 14px;
font-size:12px;
}
/* Icon button */
.mx-btn-icon{
width:36px;height:36px;padding:0;
border-radius:var(--mx-r-sm);
}
/* Sizes */
.mx-btn-sm{height:30px;padding:0 12px;font-size:12px;border-radius:var(--mx-r-xs)}
.mx-btn-lg{height:42px;padding:0 22px;font-size:14px}
/* Disabled */
.mx-btn[disabled]{
opacity:0.4;cursor:not-allowed;pointer-events:none;
}
/* Mobile: 加大触控区 */
/* ═══ DARK MODE — button pressed inner shadow ═══ */
[data-theme="dark"] .mx-btn:active{box-shadow:inset 0 2px 4px rgba(0,0,0,0.3)}
@media(max-width:767px){
.mx-btn{height:40px;padding:0 20px;font-size:14px}
.mx-btn-sm{height:36px;padding:0 14px;font-size:13px}
.mx-btn-lg{height:46px;padding:0 24px;font-size:15px}
.mx-btn-icon{width:40px;height:40px}
.mx-btn-pill{height:34px;padding:0 16px;font-size:13px}
}
+67
View File
@@ -0,0 +1,67 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Card Component
对应 Miuix Card.kt
CornerRadius = 16.dp, surfaceContainer 背景
═══════════════════════════════════════════ */
.mx-card{
background:var(--mx-s2);
border-radius:var(--mx-r);
overflow:hidden;
box-shadow:var(--mx-sh1);
transition:box-shadow var(--mx-d3) var(--mx-ease);
}
.mx-card:hover{
box-shadow:var(--mx-sh2);
transition-duration:var(--mx-d2);
}
.mx-card-head{
display:flex;align-items:center;justify-content:space-between;
padding:12px 16px;
border-bottom:1px solid var(--mx-divider);
}
.mx-card-title{font-size:15px;font-weight:700}
.mx-card-body{padding:16px}
.mx-card-body > * + * { margin-top: 12px; }
.mx-card-body > .mx-table-wrap { margin-top: 0; }
.mx-card-body > .mx-chart { margin-top: 0; }
/* 可点击卡片 — Miuix BasicCard with press feedback */
.mx-card-clickable{
cursor:pointer;
transition:box-shadow var(--mx-d3) var(--mx-ease),
transform var(--mx-d3) var(--mx-ease);
}
.mx-card-clickable:hover{
transition-duration:var(--mx-d2);
}
.mx-card-clickable:active{transform:scale(0.98)}
/* Mobile: 卡片内边距调整 */
@media(max-width:767px){
.mx-card-body{padding:14px}
.mx-card-head{padding:12px 14px}
}
/* Miuix: 顶部 accent 线 */
.mx-card-accent{
position:relative;overflow:hidden;
}
.mx-card-accent::before{
content:'';position:absolute;top:0;left:0;right:0;height:2px;
background:var(--mx-card-accent,var(--mx-primary));
opacity:0;transition:opacity var(--mx-d2);
}
.mx-card-accent:hover::before{opacity:1}
/* Miuix: 右上角柔光晕 */
.mx-card-accent::after{
content:'';position:absolute;top:-40%;right:-20%;
width:100px;height:100px;
background:var(--mx-card-accent,var(--mx-primary));
border-radius:50%;opacity:0.04;
transition:opacity var(--mx-d2);
}
.mx-card-accent:hover::after{opacity:0.08}
+139
View File
@@ -0,0 +1,139 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Checkbox & Radio Component
═══════════════════════════════════════════ */
/* ── Checkbox ── */
.mx-checkbox{
display:inline-flex;align-items:center;gap:8px;
cursor:pointer;
-webkit-tap-highlight-color:transparent;
}
.mx-checkbox input{display:none}
.mx-checkbox-box{
width:18px;height:18px;
border:2px solid var(--mx-border);
border-radius:5px;
display:flex;align-items:center;justify-content:center;
flex-shrink:0;
transition:all var(--mx-d2) var(--mx-spring);
background:transparent;
}
.mx-checkbox-box svg{
width:12px;height:12px;
color:#fff;opacity:0;
transform:scale(0);
transition:all var(--mx-d2) var(--mx-spring);
}
/* Checked */
.mx-checkbox input:checked + .mx-checkbox-box{
background:var(--mx-primary);
border-color:var(--mx-primary);
}
.mx-checkbox input:checked + .mx-checkbox-box svg{
opacity:1;transform:scale(1);
}
/* Indeterminate */
.mx-checkbox input:indeterminate + .mx-checkbox-box{
background:var(--mx-primary);
border-color:var(--mx-primary);
}
.mx-checkbox input:indeterminate + .mx-checkbox-box svg{
opacity:1;transform:scale(1);
}
/* Hover */
.mx-checkbox:hover .mx-checkbox-box{border-color:var(--mx-primary)}
/* Disabled */
.mx-checkbox input:disabled + .mx-checkbox-box{
opacity:0.4;cursor:not-allowed;
}
.mx-checkbox input:disabled ~ .mx-checkbox-label{
opacity:0.4;cursor:not-allowed;
}
/* Focus */
.mx-checkbox input:focus-visible + .mx-checkbox-box{
box-shadow:0 0 0 3px var(--mx-primary-glow);
}
/* Label */
.mx-checkbox-label{
font-size:14px;color:var(--mx-text);
transition:color var(--mx-d1);
user-select:none;
}
/* ── Radio ── */
.mx-radio{
display:inline-flex;align-items:center;gap:8px;
cursor:pointer;
-webkit-tap-highlight-color:transparent;
}
.mx-radio input{display:none}
.mx-radio-dot{
width:18px;height:18px;
border:2px solid var(--mx-border);
border-radius:50%;
display:flex;align-items:center;justify-content:center;
flex-shrink:0;
transition:all var(--mx-d2) var(--mx-spring);
}
.mx-radio-dot::after{
content:'';width:8px;height:8px;
border-radius:50%;background:#fff;
transform:scale(0);
transition:transform var(--mx-d2) var(--mx-spring);
}
/* Checked */
.mx-radio input:checked + .mx-radio-dot{
background:var(--mx-primary);
border-color:var(--mx-primary);
}
.mx-radio input:checked + .mx-radio-dot::after{
transform:scale(1);
}
/* Hover */
.mx-radio:hover .mx-radio-dot{border-color:var(--mx-primary)}
/* Disabled */
.mx-radio input:disabled + .mx-radio-dot{
opacity:0.4;cursor:not-allowed;
}
.mx-radio input:disabled ~ .mx-radio-label{
opacity:0.4;cursor:not-allowed;
}
/* Focus */
.mx-radio input:focus-visible + .mx-radio-dot{
box-shadow:0 0 0 3px var(--mx-primary-glow);
}
/* Label */
.mx-radio-label{
font-size:14px;color:var(--mx-text);
transition:color var(--mx-d1);
user-select:none;
}
/* ── Checkbox Group / Radio Group layout ── */
.mx-checkbox-group,.mx-radio-group{
display:flex;flex-direction:column;gap:10px;
}
.mx-checkbox-group.horizontal,.mx-radio-group.horizontal{
flex-direction:row;flex-wrap:wrap;gap:16px;
}
/* Mobile: 加大触控区 */
@media(max-width:767px){
.mx-checkbox,.mx-radio{min-height:44px;padding:4px 0}
.mx-checkbox-box,.mx-radio-dot{width:22px;height:22px}
.mx-checkbox-box svg{width:14px;height:14px}
.mx-radio-dot::after{width:10px;height:10px}
}
+93
View File
@@ -0,0 +1,93 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Drawer / Side Panel Component
═══════════════════════════════════════════ */
/* Overlay */
.mx-drawer-overlay{
position:fixed;inset:0;z-index:900;
background:var(--mx-window-dim);
opacity:0;visibility:hidden;
transition:opacity var(--mx-d2) var(--mx-ease),
visibility var(--mx-d2);
}
.mx-drawer-overlay.open{opacity:1;visibility:visible}
/* Drawer */
.mx-drawer{
position:fixed;top:0;z-index:950;
width:320px;height:100dvh;
background:var(--mx-s1);
box-shadow:var(--mx-sh3);
display:flex;flex-direction:column;
transition:transform var(--mx-d3) var(--mx-spring);
}
/* Position: right (default) */
.mx-drawer-right{
right:0;
transform:translateX(100%);
}
.mx-drawer-right.open{transform:translateX(0)}
/* Position: left */
.mx-drawer-left{
left:0;
transform:translateX(-100%);
}
.mx-drawer-left.open{transform:translateX(0)}
/* Size variants */
.mx-drawer-sm{width:280px}
.mx-drawer-lg{width:420px}
@media(min-width:1024px){
.mx-drawer-xl{width:560px}
}
/* Header */
.mx-drawer-head{
display:flex;align-items:center;justify-content:space-between;
padding:16px 20px;
border-bottom:1px solid var(--mx-divider);
flex-shrink:0;
}
.mx-drawer-title{font-size:15px;font-weight:700}
.mx-drawer-close{
width:28px;height:28px;
display:flex;align-items:center;justify-content:center;
border-radius:var(--mx-r-xs);
cursor:pointer;color:var(--mx-t3);
transition:all var(--mx-d1);
}
.mx-drawer-close:hover{background:var(--mx-s-hover);color:var(--mx-text)}
/* Body */
.mx-drawer-body{
flex:1;overflow-y:auto;padding:20px;
font-size:14px;line-height:1.6;
}
/* Footer */
.mx-drawer-foot{
display:flex;align-items:center;justify-content:flex-end;gap:8px;
padding:12px 20px;
border-top:1px solid var(--mx-divider);
flex-shrink:0;
}
/* Full width on mobile */
@media(max-width:767px){
.mx-drawer{width:100%}
.mx-drawer-sm,.mx-drawer-lg,.mx-drawer-xl{width:100%}
}
/* Tablet */
@media(min-width:768px) and (max-width:1023px){
.mx-drawer{width:360px}
.mx-drawer-sm{width:300px}
.mx-drawer-lg{width:420px}
}
/* ═══ DARK MODE INNER GLOW — unified with Card/Stat ═══ */
[data-theme="dark"] .mx-drawer{
box-shadow:var(--mx-sh3),inset 0 1px 0 rgba(255,255,255,0.04);
}
+46
View File
@@ -0,0 +1,46 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Empty State Component
═══════════════════════════════════════════ */
.mx-empty{
display:flex;flex-direction:column;
align-items:center;justify-content:center;
padding:48px 24px;text-align:center;
}
.mx-empty-icon{
width:64px;height:64px;
border-radius:var(--mx-r);
background:var(--mx-s3);
display:flex;align-items:center;justify-content:center;
margin-bottom:16px;
color:var(--mx-t3);
}
.mx-empty-icon svg{width:28px;height:28px}
.mx-empty-title{
font-size:15px;font-weight:700;
color:var(--mx-text);margin-bottom:6px;
}
.mx-empty-desc{
font-size:13px;color:var(--mx-t3);
line-height:1.5;max-width:320px;margin-bottom:20px;
}
/* Inline variant — smaller */
.mx-empty-inline{
padding:24px 16px;
}
.mx-empty-inline .mx-empty-icon{
width:48px;height:48px;
}
.mx-empty-inline .mx-empty-icon svg{width:22px;height:22px}
/* Mobile */
@media(max-width:767px){
.mx-empty{padding:32px 16px}
.mx-empty-icon{width:52px;height:52px}
.mx-empty-icon svg{width:24px;height:24px}
.mx-empty-title{font-size:14px}
.mx-empty-desc{font-size:12px}
}
+77
View File
@@ -0,0 +1,77 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Input Component
═══════════════════════════════════════════ */
.mx-input-wrap{
display:flex;align-items:center;gap:10px;
background:var(--mx-s3);
border:1px solid var(--mx-border);
border-radius:var(--mx-r-sm);
padding:0 16px;height:40px;
transition:all var(--mx-d2) var(--mx-ease);
}
.mx-input-wrap:focus-within{
border-color:var(--mx-primary);
box-shadow:0 0 0 3px var(--mx-primary-glow);
background:var(--mx-s2);
}
.mx-input-wrap .mx-input-icon{
color:var(--mx-t3);flex-shrink:0;
display:flex;align-items:center;
}
.mx-input{
width:100%;height:100%;
background:none;
color:var(--mx-text);font-size:14px;
font-family:var(--mx-font);
}
.mx-input::placeholder{color:var(--mx-t3)}
/* Textarea */
.mx-textarea-wrap{
padding:12px 14px;height:auto;min-height:80px;
align-items:flex-start;
}
.mx-textarea{
width:100%;resize:vertical;min-height:60px;
background:none;border:none;outline:none;
color:var(--mx-text);font-size:14px;
font-family:var(--mx-font);line-height:1.6;
}
.mx-textarea::placeholder{color:var(--mx-t3)}
/* Sizes */
.mx-input-sm{height:34px;padding:0 12px;font-size:13px;border-radius:var(--mx-r-xs)}
.mx-input-lg{height:46px;padding:0 16px;font-size:15px}
/* Error */
.mx-input-error{border-color:var(--mx-red)}
.mx-input-error:focus-within{box-shadow:0 0 0 3px rgba(248,113,113,0.15)}
/* Disabled */
.mx-input-wrap.mx-disabled{
opacity:0.4;cursor:not-allowed;
}
.mx-input-wrap.mx-disabled input{pointer-events:none}
/* Label */
.mx-label{
display:block;font-size:12px;font-weight:600;
color:var(--mx-t2);margin-bottom:6px;
text-transform:uppercase;letter-spacing:0.04em;
}
/* Helper text */
.mx-helper{font-size:12px;color:var(--mx-t3);margin-top:4px}
.mx-helper-error{color:var(--mx-red)}
/* Mobile: 防止 iOS 自动缩放 + 加大触控区 */
@media(max-width:767px){
.mx-input-wrap{height:44px;padding:0 16px}
.mx-input{font-size:16px}
.mx-input-sm{height:38px;font-size:15px}
.mx-input-lg{height:50px;font-size:16px}
.mx-textarea-wrap{min-height:88px}
.mx-textarea{font-size:16px;min-height:72px}
.mx-label{font-size:13px;margin-bottom:8px}
}
+327
View File
@@ -0,0 +1,327 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Layout System
全设备适配:手机 / 平板 / 桌面 / 大屏
═══════════════════════════════════════════ */
/* ── App Shell ── */
.mx-app{display:flex;flex-direction:column;height:100dvh}
/* ── Scrollable Body ── */
.mx-body{
flex:1;overflow-y:auto;overflow-x:hidden;
-webkit-overflow-scrolling:touch;
}
/* ── 自动为手机端主内容区添加底部内边距,防止 fixed 底栏遮挡 ── */
@media(max-width:1023px){
.mx-app > .mx-body,
.mx-app > .mx-desktop-main.mx-body{
padding-bottom:calc(72px + var(--mx-safe-b));
}
}
@media(min-width:1024px){
.mx-app > .mx-body,
.mx-app > .mx-desktop-main.mx-body{
padding-bottom:0;
}
}
/* ── Padding utilities ── */
.mx-pad{padding:12px}
.mx-pad-b{padding-bottom:calc(72px + var(--mx-safe-b))}
@media(min-width:768px){.mx-pad{padding:20px}.mx-pad-b{padding-bottom:20px}}
@media(min-width:1024px){.mx-pad{padding:28px}}
/* ── Page Header ── */
.mx-page-head{margin-bottom:16px}
@media(min-width:768px){.mx-page-head{margin-bottom:20px}}
.mx-page-title{
font-size:20px;font-weight:800;letter-spacing:-0.02em;
background:linear-gradient(135deg,var(--mx-text),var(--mx-t2));
-webkit-background-clip:text;-webkit-text-fill-color:transparent;
}
@media(min-width:768px){.mx-page-title{font-size:24px}}
.mx-page-sub{
font-size:12px;color:var(--mx-t3);margin-top:4px;
display:flex;align-items:center;gap:6px;
}
/* ── Section ── */
.mx-section{margin-bottom:12px}
@media(min-width:768px){.mx-section{margin-bottom:16px}}
/* ══════════════════════════════════════════
MOBILE (< 768px) — 触控优先
══════════════════════════════════════════ */
@media(max-width:767px){
/* 触控友好的最小高度 */
.mx-btn{height:40px;padding:0 20px;font-size:14px}
.mx-btn-sm{height:36px;padding:0 14px;font-size:13px}
.mx-btn-lg{height:46px;padding:0 24px;font-size:15px}
.mx-btn-icon{width:40px;height:40px}
/* 输入框加大 */
.mx-input-wrap{height:44px;padding:0 16px}
.mx-input{font-size:16px} /* 防止 iOS 自动缩放 */
.mx-input-sm{height:38px;font-size:15px}
.mx-input-lg{height:50px;font-size:16px}
/* Select 加大 */
.mx-select-trigger{height:44px;font-size:16px}
/* Switch 加大 */
.mx-switch-track{width:48px;height:28px}
.mx-switch-thumb{width:22px;height:22px;top:3px;left:3px}
.mx-switch input:checked + .mx-switch-track .mx-switch-thumb{transform:translateX(20px)}
/* Checkbox/Radio 加大触控区 */
.mx-checkbox-box,.mx-radio-dot{width:20px;height:20px}
.mx-checkbox-box svg{width:14px;height:14px}
.mx-radio-dot::after{width:9px;height:9px}
/* Slider 触控加大 */
.mx-slider input[type="range"]::-webkit-slider-thumb{width:22px;height:22px}
.mx-slider input[type="range"]::-moz-range-thumb{width:22px;height:22px}
/* Navigation 触控加大 */
.mx-nav-item{padding:11px 10px}
.mx-btm-item{padding:8px 16px}
.mx-btm-item span{font-size:11px}
/* TopBar */
.mx-topbar{height:48px;padding:0 12px}
/* BottomNav */
.mx-bottom-nav{height:auto}
/* Modal 全屏化 */
.mx-modal-overlay{padding:0;align-items:flex-end}
.mx-modal{
max-width:100%;max-height:90dvh;
border-radius:var(--mx-r) var(--mx-r) 0 0;
transform:translateY(100%);
opacity:1;
}
.mx-modal-overlay.open .mx-modal{transform:translateY(0)}
.mx-modal-sm,.mx-modal-lg,.mx-modal-xl{max-width:100%}
/* Drawer 全宽 */
.mx-drawer{width:100%}
.mx-drawer-sm,.mx-drawer-lg,.mx-drawer-xl{width:100%}
/* Grid 单列降级 */
.mx-grid-2,.mx-grid-3{grid-template-columns:1fr}
.mx-grid-sidebar{grid-template-columns:1fr}
/* Stat 单列(窄屏手机) */
.mx-stat-grid{grid-template-columns:1fr 1fr}
/* Table 卡片式响应 */
.mx-table{border:0}
.mx-table thead{display:none}
.mx-table tbody tr{
display:block;margin-bottom:12px;
border:1px solid var(--mx-divider);
border-radius:var(--mx-r-sm);
padding:12px;
background:var(--mx-s2);
}
.mx-table tbody td{
display:flex;justify-content:space-between;align-items:center;
padding:6px 0;border-bottom:1px solid var(--mx-divider);
white-space:normal;
}
.mx-table tbody td:last-child{border-bottom:none}
.mx-table tbody td::before{
content:attr(data-label);
font-size:11px;font-weight:700;color:var(--mx-t3);
text-transform:uppercase;letter-spacing:0.04em;
flex-shrink:0;margin-right:12px;
}
.mx-table tbody tr:hover td{padding-left:0}
/* Tabs 横向滚动 */
.mx-tabs{overflow-x:auto;-webkit-overflow-scrolling:touch}
.mx-tab{padding:10px 14px}
/* Pagination 精简 */
.mx-page-prev span,.mx-page-next span{display:none}
.mx-page-info{display:none}
.mx-pagination{gap:2px}
.mx-page-btn{width:32px;height:32px;font-size:12px}
/* Popover 全宽 */
.mx-popover{
position:fixed;left:8px!important;right:8px!important;
bottom:8px!important;top:auto!important;
width:auto!important;max-width:none;
transform:translateY(100%)!important;
transform-origin:bottom center;
border-radius:var(--mx-r);
}
.mx-popover.open{transform:translateY(0)!important}
/* Select dropdown 全宽 */
.mx-select-dropdown{
position:fixed;left:8px!important;right:8px!important;
bottom:8px!important;top:auto!important;
max-height:50dvh;
transform:translateY(100%)!important;
transform-origin:bottom center;
border-radius:var(--mx-r);
}
.mx-select-dropdown.open{transform:translateY(0)!important}
/* Datepicker dropdown 全宽 */
.mx-datepicker-dropdown{
position:fixed;left:8px!important;right:8px!important;
bottom:8px!important;top:auto!important;
transform:translateY(100%)!important;
transform-origin:bottom center;
border-radius:var(--mx-r);
}
.mx-datepicker-dropdown.open{transform:translateY(0)!important}
/* Accordion */
.mx-accordion-header{padding:12px 14px}
/* Upload */
.mx-upload{padding:24px 16px}
/* Avatar group */
.mx-avatar-group .mx-avatar,
.mx-avatar-group .mx-avatar-img{margin-left:-10px}
}
/* ══════════════════════════════════════════
TABLET (768px — 1023px)
══════════════════════════════════════════ */
@media(min-width:768px) and (max-width:1023px){
.mx-grid-3{grid-template-columns:1fr 1fr}
.mx-grid-sidebar{grid-template-columns:1fr}
.mx-stat-grid{grid-template-columns:repeat(4,1fr)}
/* Modal 稍宽 */
.mx-modal{max-width:480px}
/* Drawer */
.mx-drawer{width:360px}
.mx-drawer-sm{width:300px}
.mx-drawer-lg{width:420px}
/* Tabs 横向滚动 */
.mx-tabs{overflow-x:auto;-webkit-overflow-scrolling:touch}
}
/* ══════════════════════════════════════════
DESKTOP (≥ 1024px)
══════════════════════════════════════════ */
@media(min-width:1024px){
.mx-app{flex-direction:row}
.mx-app>.mx-topbar{display:none}
.mx-app>.mx-bottom-nav{display:none!important}
.mx-desktop-shell{
display:flex;flex-direction:column;width:100%;
}
.mx-desktop-topbar{
display:flex;align-items:center;justify-content:space-between;
height:52px;padding:0 24px;
border-bottom:1px solid var(--mx-divider);
backdrop-filter:blur(24px) saturate(1.8);
-webkit-backdrop-filter:blur(24px) saturate(1.8);
flex-shrink:0;
}
[data-theme="dark"] .mx-desktop-topbar{background:rgba(0,0,0,0.6)}
[data-theme="light"] .mx-desktop-topbar{background:rgba(247,247,247,0.65)}
.mx-desktop-main{
flex:1;padding:28px;overflow-y:auto;background:var(--mx-bg);
}
.mx-sidebar{display:block}
.mx-bottom-nav{display:none!important}
/* Grid 正常 */
.mx-grid-2{grid-template-columns:1fr 1fr}
.mx-grid-3{grid-template-columns:1fr 1fr 1fr}
.mx-grid-sidebar{display:grid;grid-template-columns:1fr 320px;gap:16px}
.mx-stat-grid{grid-template-columns:repeat(4,1fr)}
}
/* Hide desktop elements on mobile */
@media(max-width:1023px){
.mx-sidebar{display:none}
.mx-desktop-shell{display:none}
}
/* ── Wide (≥1440px) ── */
@media(min-width:1440px){
.mx-sidebar{width:260px}
.mx-desktop-main{padding:28px 32px}
}
/* ══════════════════════════════════════════
TOUCH DETECTION — 精确触控增强
══════════════════════════════════════════ */
@media(hover:none) and (pointer:coarse){
/* 触控设备额外加大可交互区域 */
.mx-tab{min-height:44px}
.mx-nav-item{min-height:44px}
.mx-accordion-header{min-height:48px}
.mx-popover-item{min-height:44px}
.mx-select-option{min-height:44px}
.mx-page-btn{min-height:36px}
.mx-tag{min-height:32px}
/* 禁用 hover 效果(触控无 hover) */
.mx-card:hover{box-shadow:var(--mx-sh1)}
.mx-stat:hover::before{opacity:0}
.mx-stat:hover::after{opacity:0.04}
.mx-stat:hover .mx-stat-icon{transform:none}
.mx-table tbody tr:hover{background:transparent}
.mx-table tbody tr:hover td{padding-left:14px}
.mx-table th:hover::after{transform:scaleX(0)}
/* 用 :active 替代 */
.mx-card:active{box-shadow:var(--mx-sh2)}
.mx-stat:active{box-shadow:var(--mx-sh2)}
.mx-table tbody tr:active{background:var(--mx-s-hover)}
/* Tooltip 改为长按触发 */
.mx-tooltip::after{
transition:none;
opacity:0;visibility:hidden;
}
.mx-tooltip:active::after{
opacity:1;visibility:visible;
}
}
/* ══════════════════════════════════════════
LANDSCAPE PHONE — 横屏手机
══════════════════════════════════════════ */
@media(max-height:500px) and (orientation:landscape){
.mx-topbar{height:40px}
.mx-bottom-nav{display:none!important}
.mx-modal{max-height:95dvh;border-radius:var(--mx-r)}
.mx-modal-overlay{align-items:center;padding:8px}
.mx-drawer{height:100dvh}
}
/* ══════════════════════════════════════════
FLEX utilities
══════════════════════════════════════════ */
.mx-flex{display:flex}
.mx-flex-center{display:flex;align-items:center;justify-content:center}
.mx-flex-between{display:flex;align-items:center;justify-content:space-between}
.mx-gap-4{gap:4px}.mx-gap-8{gap:8px}.mx-gap-12{gap:12px}.mx-gap-16{gap:16px}
/* ══════════════════════════════════════════
SAFE AREA — 全面屏设备
══════════════════════════════════════════ */
@supports(padding:max(0px)){
.mx-pad{padding:max(12px, env(safe-area-inset-left)) max(12px, env(safe-area-inset-right))}
@media(min-width:768px){.mx-pad{padding:max(20px, env(safe-area-inset-left)) max(20px, env(safe-area-inset-right))}}
.mx-bottom-nav{padding-bottom:max(4px, env(safe-area-inset-bottom))}
}
+117
View File
@@ -0,0 +1,117 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Modal / Dialog Component
对应 Miuix Dialog — 多层 Surface + 弹簧入场
═══════════════════════════════════════════ */
/* Overlay */
.mx-modal-overlay{
position:fixed;inset:0;z-index:1000;
display:flex;align-items:center;justify-content:center;
background:var(--mx-window-dim);
opacity:0;visibility:hidden;
transition:opacity var(--mx-d2) var(--mx-ease),
visibility var(--mx-d2);
padding:16px;
}
.mx-modal-overlay.open{
opacity:1;visibility:visible;
}
/* Dialog */
.mx-modal{
background:var(--mx-s2);
border-radius:var(--mx-r);
box-shadow:var(--mx-sh3);
width:100%;max-width:420px;
max-height:calc(100dvh - 48px);
overflow:hidden;
display:flex;flex-direction:column;
transform:scale(0.92) translateY(16px);
opacity:0;
transition:transform var(--mx-d3) var(--mx-spring),
opacity var(--mx-d2) var(--mx-ease);
}
.mx-modal-overlay.open .mx-modal{
transform:scale(1) translateY(0);
opacity:1;
}
/* Size variants */
.mx-modal-sm{max-width:340px}
.mx-modal-lg{max-width:560px}
.mx-modal-xl{max-width:720px}
.mx-modal-full{max-width:calc(100dvw - 32px);max-height:calc(100dvh - 32px)}
/* Header */
.mx-modal-head{
display:flex;align-items:center;justify-content:space-between;
padding:16px 20px;
border-bottom:1px solid var(--mx-divider);
flex-shrink:0;
}
.mx-modal-title{
font-size:15px;font-weight:700;
}
.mx-modal-close{
width:28px;height:28px;
display:flex;align-items:center;justify-content:center;
border-radius:var(--mx-r-xs);
cursor:pointer;color:var(--mx-t3);
transition:all var(--mx-d1) var(--mx-ease);
}
.mx-modal-close:hover{background:var(--mx-s-hover);color:var(--mx-text)}
.mx-modal-close:active{transform:scale(0.9)}
/* Body */
.mx-modal-body{
padding:20px;
overflow-y:auto;flex:1;
font-size:14px;line-height:1.6;color:var(--mx-text-on-surface);
}
/* Footer */
.mx-modal-foot{
display:flex;align-items:center;justify-content:flex-end;gap:8px;
padding:12px 20px;
border-top:1px solid var(--mx-divider);
flex-shrink:0;
}
/* Confirmation variant — icon + centered text */
.mx-modal-confirm-icon{
width:48px;height:48px;
border-radius:50%;
display:flex;align-items:center;justify-content:center;
margin:0 auto 16px;
}
.mx-modal-confirm-icon.info{background:rgba(96,165,250,0.12);color:var(--mx-blue)}
.mx-modal-confirm-icon.success{background:rgba(52,211,153,0.12);color:var(--mx-green)}
.mx-modal-confirm-icon.warning{background:rgba(251,191,36,0.12);color:var(--mx-amber)}
.mx-modal-confirm-icon.danger{background:rgba(248,113,113,0.12);color:var(--mx-red)}
.mx-modal-confirm-text{
text-align:center;font-size:14px;color:var(--mx-text-on-surface);
line-height:1.6;margin-bottom:4px;
}
.mx-modal-confirm-title{
text-align:center;font-size:16px;font-weight:700;
margin-bottom:8px;
}
.mx-modal-confirm-foot{justify-content:center}
/* Mobile: 按钮全宽堆叠 */
@media(max-width:767px){
.mx-modal-foot{flex-direction:column-reverse}
.mx-modal-foot .mx-btn{width:100%}
}
/* Tablet: 居中弹窗 */
@media(min-width:768px) and (max-width:1023px){
.mx-modal-overlay{align-items:center;padding:16px}
.mx-modal{max-width:480px;transform:scale(0.92) translateY(16px)}
.mx-modal-overlay.open .mx-modal{transform:scale(1) translateY(0)}
}
/* ═══ DARK MODE INNER GLOW — unified with Card/Stat ═══ */
[data-theme="dark"] .mx-modal{
box-shadow:var(--mx-sh3),inset 0 1px 0 rgba(255,255,255,0.04);
}
+212
View File
@@ -0,0 +1,212 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Navigation Components
TopBar + Sidebar + BottomNav
═══════════════════════════════════════════ */
/* ── TopBar — Miuix frosted glass ── */
.mx-topbar{
position:sticky;top:0;z-index:100;
display:flex;align-items:center;justify-content:space-between;
height:52px;padding:0 16px;
padding-top:var(--mx-safe-t);
border-bottom:1px solid var(--mx-divider);
backdrop-filter:blur(24px) saturate(1.8);
-webkit-backdrop-filter:blur(24px) saturate(1.8);
flex-shrink:0;
}
[data-theme="dark"] .mx-topbar{background:rgba(0,0,0,0.6)}
[data-theme="light"] .mx-topbar{background:rgba(247,247,247,0.65)}
.mx-topbar-left{display:flex;align-items:center;gap:10px}
.mx-topbar-right{display:flex;align-items:center;gap:4px}
/* Logo */
.mx-logo{
display:flex;align-items:center;gap:10px;
}
.mx-logo-icon{
width:32px;height:32px;
background:linear-gradient(135deg,var(--mx-primary),var(--mx-accent));
border-radius:10px;
display:flex;align-items:center;justify-content:center;
transition:transform var(--mx-d2) var(--mx-ease);
box-shadow:0 2px 8px rgba(76,139,245,0.2);
flex-shrink:0;
}
.mx-logo-icon:active{transform:scale(0.92)}
.mx-logo-icon svg{color:#fff}
.mx-logo-text{font-size:16px;font-weight:700}
/* ── Sidebar ── */
.mx-sidebar{
width:240px;flex-shrink:0;
background:var(--mx-s1);
border-right:1px solid var(--mx-divider);
padding:12px;
overflow-y:auto;scrollbar-width:none;
}
.mx-sidebar::-webkit-scrollbar{display:none}
.mx-sidebar-section{
font-size:11px;font-weight:700;color:var(--mx-t3);
text-transform:uppercase;letter-spacing:0.06em;
padding:0 10px;margin:16px 0 4px;
}
.mx-sidebar-section:first-child{margin-top:4px}
.mx-nav-item{
display:flex;align-items:center;gap:10px;
padding:8px 10px;border-radius:var(--mx-r-sm);
cursor:pointer;color:var(--mx-t2);
font-size:13px;font-weight:500;
transition:all var(--mx-d1) var(--mx-ease);
position:relative;
-webkit-tap-highlight-color:transparent;
}
.mx-nav-item::before{
content:'';position:absolute;left:0;top:50%;
width:3px;height:0;
background:var(--mx-primary);
border-radius:0 3px 3px 0;
transform:translateY(-50%);
transition:height var(--mx-d2) var(--mx-spring);
}
.mx-nav-item:hover{background:var(--mx-s-hover);color:var(--mx-text)}
.mx-nav-item.active{background:var(--mx-primary-glow);color:var(--mx-primary)}
.mx-nav-item.active::before{height:18px}
.mx-nav-item:active{transform:scale(0.97)}
.mx-nav-item svg{flex-shrink:0}
.mx-nav-label{flex:1}
.mx-sidebar-bottom{
margin-top:auto;padding-top:12px;
border-top:1px solid var(--mx-divider);
}
/* ── BottomNav — mobile only ── */
.mx-bottom-nav{
position:fixed;bottom:0;left:0;right:0;z-index:200;
border-top:1px solid var(--mx-divider);
backdrop-filter:blur(24px) saturate(1.8);
-webkit-backdrop-filter:blur(24px) saturate(1.8);
padding:4px 0;
padding-bottom:calc(4px + var(--mx-safe-b));
}
[data-theme="dark"] .mx-bottom-nav{background:rgba(0,0,0,0.6)}
[data-theme="light"] .mx-bottom-nav{background:rgba(247,247,247,0.65)}
.mx-bottom-nav-items{
display:flex;justify-content:space-around;
}
.mx-btm-item{
display:flex;flex-direction:column;align-items:center;gap:2px;
padding:6px 16px;border-radius:var(--mx-r-sm);
cursor:pointer;color:var(--mx-t3);
transition:all var(--mx-d2) var(--mx-spring);
-webkit-tap-highlight-color:transparent;
position:relative;
}
.mx-btm-item.active{color:var(--mx-primary)}
.mx-btm-item.active::before{
content:'';position:absolute;top:2px;left:50%;
width:5px;height:5px;
background:var(--mx-primary);
border-radius:50%;transform:translateX(-50%);
animation:mx-dot-in var(--mx-d2) var(--mx-spring);
}
.mx-btm-item span{font-size:10px;font-weight:600}
.mx-btm-item:active{transform:scale(0.88)}
@keyframes mx-dot-in{
from{transform:translateX(-50%) scale(0);opacity:0}
to{transform:translateX(-50%) scale(1);opacity:1}
}
/* ── Breadcrumb ── */
.mx-breadcrumb{
display:flex;align-items:center;gap:8px;
font-size:13px;color:var(--mx-t3);
}
.mx-breadcrumb span{transition:color var(--mx-d1)}
.mx-breadcrumb span:last-child{color:var(--mx-t2);font-weight:500}
.mx-breadcrumb .mx-breadcrumb-sep{color:var(--mx-t3);font-size:11px}
/* ── Pill Group — density/theme toggle ── */
.mx-pill-group{
display:inline-flex;
background:var(--mx-s3);
border-radius:var(--mx-r-pill);
padding:3px;gap:2px;
}
.mx-pill-group button{
padding:5px 14px;border-radius:var(--mx-r-pill);
font-size:12px;font-weight:600;cursor:pointer;
color:var(--mx-t3);
transition:all var(--mx-d2) var(--mx-ease);
}
.mx-pill-group button.active{
background:var(--mx-primary);color:#fff;
box-shadow:0 2px 8px rgba(76,139,245,0.25);
}
.mx-pill-group button:hover:not(.active){color:var(--mx-text)}
/* ── Avatar ── */
.mx-avatar{
width:32px;height:32px;border-radius:50%;
background:linear-gradient(135deg,var(--mx-primary),var(--mx-accent));
display:flex;align-items:center;justify-content:center;
font-size:13px;font-weight:700;color:#fff;
cursor:pointer;flex-shrink:0;
transition:all var(--mx-d2) var(--mx-ease);
box-shadow:0 0 0 0 var(--mx-primary-glow);
}
.mx-avatar:hover{box-shadow:0 0 0 4px var(--mx-primary-glow)}
.mx-avatar:active{transform:scale(0.9)}
/* ── Notification Dot ── */
.mx-notify{
position:relative;
}
.mx-notify .mx-notify-dot{
position:absolute;top:7px;right:7px;
width:7px;height:7px;
background:var(--mx-red);
border-radius:50%;
border:2px solid var(--mx-s1);
animation:mx-pulse 2s infinite;
}
/* ═══ MOBILE ADAPTATIONS ═══ */
@media(max-width:767px){
/* TopBar 更紧凑 */
.mx-topbar{height:48px;padding:0 12px}
.mx-logo-icon{width:28px;height:28px;border-radius:8px}
.mx-logo-text{font-size:14px}
.mx-avatar{width:28px;height:28px;font-size:12px}
/* BottomNav 加大触控区 */
.mx-btm-item{padding:8px 16px;min-width:56px}
.mx-btm-item span{font-size:11px}
.mx-btm-item svg{width:24px;height:24px}
/* Sidebar section */
.mx-sidebar-section{font-size:10px}
.mx-nav-item{padding:11px 10px;min-height:44px}
/* Breadcrumb 滚动 */
.mx-breadcrumb{overflow-x:auto;white-space:nowrap;-webkit-overflow-scrolling:touch}
/* Pill group */
.mx-pill-group button{padding:6px 12px;font-size:11px}
/* Notification dot */
.mx-notify .mx-notify-dot{top:5px;right:5px;width:6px;height:6px}
}
/* ═══ TABLET ADAPTATIONS ═══ */
@media(min-width:768px) and (max-width:1023px){
.mx-topbar{height:50px}
.mx-bottom-nav{padding:6px 0}
.mx-btm-item span{font-size:10px}
}
+83
View File
@@ -0,0 +1,83 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Pagination Component
═══════════════════════════════════════════ */
.mx-pagination{
display:flex;align-items:center;gap:4px;
font-size:13px;
}
/* Page button */
.mx-page-btn{
width:34px;height:34px;
display:flex;align-items:center;justify-content:center;
border-radius:var(--mx-r-xs);
cursor:pointer;color:var(--mx-t2);
font-weight:500;
transition:all var(--mx-d1) var(--mx-ease);
-webkit-tap-highlight-color:transparent;
}
.mx-page-btn:hover{background:var(--mx-s-hover);color:var(--mx-text)}
.mx-page-btn:active{transform:scale(0.92)}
/* Active */
.mx-page-btn.active{
background:var(--mx-primary);color:#fff;
box-shadow:0 2px 8px rgba(76,139,245,0.25);
}
/* Disabled */
.mx-page-btn.disabled{
opacity:0.3;pointer-events:none;
}
/* Ellipsis */
.mx-page-ellipsis{
width:34px;height:34px;
display:flex;align-items:center;justify-content:center;
color:var(--mx-t3);font-size:12px;
letter-spacing:2px;
}
/* Prev / Next with text */
.mx-page-prev,.mx-page-next{
display:flex;align-items:center;gap:4px;
padding:0 10px;height:34px;
border-radius:var(--mx-r-xs);
cursor:pointer;color:var(--mx-t2);
font-size:13px;font-weight:500;
transition:all var(--mx-d1) var(--mx-ease);
}
.mx-page-prev:hover,.mx-page-next:hover{
background:var(--mx-s-hover);color:var(--mx-text);
}
.mx-page-prev:active,.mx-page-next:active{transform:scale(0.95)}
.mx-page-prev.disabled,.mx-page-next.disabled{
opacity:0.3;pointer-events:none;
}
/* Size info */
.mx-page-info{
font-size:12px;color:var(--mx-t3);
margin:0 8px;
}
/* Simple variant — just prev/next + info */
.mx-pagination-simple .mx-page-btn{display:none}
.mx-pagination-simple .mx-page-ellipsis{display:none}
/* Mobile: 精简显示 */
@media(max-width:767px){
.mx-page-prev span,.mx-page-next span{display:none}
.mx-page-info{display:none}
.mx-pagination{gap:2px}
.mx-page-btn{width:32px;height:32px;font-size:12px}
.mx-page-prev,.mx-page-next{padding:0 6px;height:32px}
/* 只显示前 5 页 */
.mx-page-btn:nth-child(n+8){display:none}
}
/* Tablet */
@media(min-width:768px) and (max-width:1023px){
.mx-page-btn{width:36px;height:36px}
}
+87
View File
@@ -0,0 +1,87 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Popover Component
═══════════════════════════════════════════ */
.mx-popover-wrap{position:relative;display:inline-flex}
.mx-popover{
position:absolute;z-index:600;
min-width:180px;
background:var(--mx-s4);
border:1px solid var(--mx-divider);
border-radius:var(--mx-r-sm);
box-shadow:var(--mx-sh3);
padding:6px;
opacity:0;visibility:hidden;
transform:scale(0.95) translateY(-4px);
transform-origin:top center;
transition:all var(--mx-d2) var(--mx-spring);
pointer-events:none;
}
.mx-popover.open{
opacity:1;visibility:visible;
transform:scale(1) translateY(0);
pointer-events:auto;
}
/* Position: bottom */
.mx-popover-bottom{
top:calc(100% + 6px);left:50%;
transform:translateX(-50%) scale(0.95) translateY(-4px);
transform-origin:top center;
}
.mx-popover-bottom.open{
transform:translateX(-50%) scale(1) translateY(0);
}
/* Position: left */
.mx-popover-left{
right:calc(100% + 6px);top:50%;
transform:translateY(-50%) scale(0.95) translateX(4px);
transform-origin:center right;
}
.mx-popover-left.open{
transform:translateY(-50%) scale(1) translateX(0);
}
/* Position: right */
.mx-popover-right{
left:calc(100% + 6px);top:50%;
transform:translateY(-50%) scale(0.95) translateX(-4px);
transform-origin:center left;
}
.mx-popover-right.open{
transform:translateY(-50%) scale(1) translateX(0);
}
/* Item */
.mx-popover-item{
display:flex;align-items:center;gap:8px;
padding:8px 10px;border-radius:var(--mx-r-xs);
font-size:13px;font-weight:500;
cursor:pointer;color:var(--mx-text);
transition:all var(--mx-d1) var(--mx-ease);
}
.mx-popover-item:hover{background:var(--mx-s-hover)}
.mx-popover-item:active{background:var(--mx-s-active);transform:scale(0.98)}
.mx-popover-item svg{flex-shrink:0;color:var(--mx-t2)}
/* Divider */
.mx-popover-divider{
height:1px;background:var(--mx-divider);
margin:4px 0;
}
/* Danger item */
.mx-popover-item.danger{color:var(--mx-red)}
.mx-popover-item.danger svg{color:var(--mx-red)}
/* Mobile: 底部弹出 + 加大触控 */
@media(max-width:767px){
.mx-popover-item{padding:12px 14px;min-height:44px;font-size:14px}
}
/* ═══ DARK MODE INNER GLOW — unified with Card/Stat ═══ */
[data-theme="dark"] .mx-popover{
box-shadow:var(--mx-sh3),inset 0 1px 0 rgba(255,255,255,0.04);
}
+135
View File
@@ -0,0 +1,135 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Progress Component
进度条 + 加载指示器
═══════════════════════════════════════════ */
/* ── Linear Progress ── */
.mx-progress{
height:4px;background:var(--mx-s3);
border-radius:var(--mx-r-pill);
overflow:hidden;
position:relative;
}
.mx-progress-bar{
height:100%;
background:var(--mx-primary);
border-radius:var(--mx-r-pill);
transition:width var(--mx-d3) var(--mx-ease);
position:relative;
}
/* Striped */
.mx-progress-striped .mx-progress-bar{
background-image:linear-gradient(
45deg,
rgba(255,255,255,0.15) 25%,
transparent 25%,
transparent 50%,
rgba(255,255,255,0.15) 50%,
rgba(255,255,255,0.15) 75%,
transparent 75%
);
background-size:16px 16px;
}
/* Animated striped */
.mx-progress-animated .mx-progress-bar{
animation:mx-progress-stripes 0.8s linear infinite;
}
@keyframes mx-progress-stripes{
from{background-position:16px 0}
to{background-position:0 0}
}
/* Indeterminate */
.mx-progress-indeterminate{
overflow:hidden;
}
.mx-progress-indeterminate .mx-progress-bar{
width:30%;
animation:mx-progress-slide 1.5s var(--mx-ease) infinite;
}
@keyframes mx-progress-slide{
0%{transform:translateX(-100%)}
100%{transform:translateX(400%)}
}
/* Color variants */
.mx-progress-success .mx-progress-bar{background:var(--mx-green)}
.mx-progress-warning .mx-progress-bar{background:var(--mx-amber)}
.mx-progress-error .mx-progress-bar{background:var(--mx-red)}
/* Size */
.mx-progress-sm{height:2px}
.mx-progress-lg{height:8px}
/* With label */
.mx-progress-label{
display:flex;align-items:center;justify-content:space-between;
margin-bottom:6px;
font-size:12px;font-weight:600;color:var(--mx-t2);
}
.mx-progress-label .mx-progress-value{
color:var(--mx-primary);font-variant-numeric:tabular-nums;
}
/* ── Circular Progress (Spinner) ── */
.mx-spinner{
width:24px;height:24px;
border:2.5px solid var(--mx-s3);
border-top-color:var(--mx-primary);
border-radius:50%;
animation:mx-spin 0.7s linear infinite;
}
@keyframes mx-spin{to{transform:rotate(360deg)}}
.mx-spinner-sm{width:18px;height:18px;border-width:2px}
.mx-spinner-lg{width:36px;height:36px;border-width:3px}
/* Dots spinner */
.mx-spinner-dots{
display:flex;gap:4px;
}
.mx-spinner-dots span{
width:6px;height:6px;
border-radius:50%;
background:var(--mx-primary);
animation:mx-dot-bounce 1.2s ease-in-out infinite;
}
.mx-spinner-dots span:nth-child(2){animation-delay:0.15s}
.mx-spinner-dots span:nth-child(3){animation-delay:0.3s}
@keyframes mx-dot-bounce{
0%,80%,100%{transform:scale(0.6);opacity:0.4}
40%{transform:scale(1);opacity:1}
}
/* ── Progress Ring (circular) ── */
.mx-progress-ring{
display:inline-flex;align-items:center;justify-content:center;
position:relative;
}
.mx-progress-ring svg{
transform:rotate(-90deg);
}
.mx-progress-ring-track{
fill:none;stroke:var(--mx-s3);stroke-width:4;
}
.mx-progress-ring-bar{
fill:none;stroke:var(--mx-primary);stroke-width:4;
stroke-linecap:round;
transition:stroke-dashoffset var(--mx-d3) var(--mx-ease);
}
.mx-progress-ring-text{
position:absolute;
font-size:12px;font-weight:700;
color:var(--mx-text);
font-variant-numeric:tabular-nums;
}
/* Mobile */
@media(max-width:767px){
.mx-progress{height:6px}
.mx-progress-lg{height:10px}
.mx-spinner{width:28px;height:28px}
.mx-spinner-lg{width:40px;height:40px}
}
+130
View File
@@ -0,0 +1,130 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Select / Dropdown Component
═══════════════════════════════════════════ */
/* Trigger */
.mx-select{
position:relative;display:inline-flex;
}
.mx-select-trigger{
display:flex;align-items:center;gap:8px;
height:40px;padding:0 16px;
background:var(--mx-s3);
border:1px solid var(--mx-border);
border-radius:var(--mx-r-sm);
font-size:14px;color:var(--mx-text);
cursor:pointer;
transition:all var(--mx-d2) var(--mx-ease);
min-width:160px;
}
.mx-select-trigger:hover{border-color:var(--mx-t3)}
.mx-select-trigger.open{
border-color:var(--mx-primary);
box-shadow:0 0 0 3px var(--mx-primary-glow);
}
.mx-select-trigger .mx-select-placeholder{color:var(--mx-t3);flex:1}
.mx-select-trigger .mx-select-value{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mx-select-trigger .mx-select-arrow{
color:var(--mx-t3);flex-shrink:0;
transition:transform var(--mx-d2) var(--mx-ease);
}
.mx-select-trigger.open .mx-select-arrow{transform:rotate(180deg)}
/* Sizes */
.mx-select-sm .mx-select-trigger{height:34px;padding:0 12px;font-size:13px;border-radius:var(--mx-r-xs)}
.mx-select-lg .mx-select-trigger{height:46px;padding:0 16px;font-size:15px}
/* Dropdown */
.mx-select-dropdown{
position:absolute;top:calc(100% + 4px);left:0;z-index:700;
min-width:100%;max-height:280px;
background:var(--mx-s4);
border:1px solid var(--mx-divider);
border-radius:var(--mx-r-sm);
box-shadow:var(--mx-sh3);
padding:4px;
overflow-y:auto;
opacity:0;visibility:hidden;
transform:scale(0.96) translateY(-4px);
transform-origin:top center;
transition:all var(--mx-d2) var(--mx-spring);
}
.mx-select-dropdown.open{
opacity:1;visibility:visible;
transform:scale(1) translateY(0);
}
/* Search input inside dropdown */
.mx-select-search{
display:flex;align-items:center;gap:8px;
padding:8px 10px;margin-bottom:2px;
border-bottom:1px solid var(--mx-divider);
}
.mx-select-search input{
flex:1;background:none;border:none;outline:none;
font-size:13px;color:var(--mx-text);
}
.mx-select-search input::placeholder{color:var(--mx-t3)}
.mx-select-search svg{color:var(--mx-t3);flex-shrink:0}
/* Option */
.mx-select-option{
display:flex;align-items:center;gap:8px;
padding:8px 10px;border-radius:var(--mx-r-xs);
font-size:13px;cursor:pointer;
color:var(--mx-text);
transition:all var(--mx-d1) var(--mx-ease);
}
.mx-select-option:hover{background:var(--mx-s-hover)}
.mx-select-option:active{background:var(--mx-s-active)}
.mx-select-option.selected{
background:var(--mx-primary-glow);color:var(--mx-primary);
font-weight:600;
}
.mx-select-option .mx-select-check{
margin-left:auto;color:var(--mx-primary);
opacity:0;transition:opacity var(--mx-d1);
}
.mx-select-option.selected .mx-select-check{opacity:1}
/* Group label */
.mx-select-group-label{
font-size:11px;font-weight:700;color:var(--mx-t3);
text-transform:uppercase;letter-spacing:0.06em;
padding:8px 10px 4px;
}
/* Disabled */
.mx-select.disabled{opacity:0.4;pointer-events:none}
/* Mobile: trigger 加大 */
@media(max-width:767px){
.mx-select-trigger{height:44px;font-size:16px}
.mx-select-sm .mx-select-trigger{height:40px;font-size:15px}
.mx-select-lg .mx-select-trigger{height:50px;font-size:16px}
.mx-select-option{min-height:44px;padding:10px}
}
/* Multi-select tags */
.mx-select-tags{
display:flex;flex-wrap:wrap;gap:4px;flex:1;
}
.mx-select-tag{
display:inline-flex;align-items:center;gap:4px;
padding:2px 8px;border-radius:var(--mx-r-pill);
background:var(--mx-primary-glow);
color:var(--mx-primary);
font-size:12px;font-weight:600;
}
.mx-select-tag-remove{
width:14px;height:14px;
display:flex;align-items:center;justify-content:center;
border-radius:50%;cursor:pointer;
transition:background var(--mx-d1);
}
.mx-select-tag-remove:hover{background:rgba(52,130,255,0.2)}
/* ═══ DARK MODE INNER GLOW — unified with Card/Stat ═══ */
[data-theme="dark"] .mx-select-dropdown{
box-shadow:var(--mx-sh3),inset 0 1px 0 rgba(255,255,255,0.04);
}
+79
View File
@@ -0,0 +1,79 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Stat Card Component
═══════════════════════════════════════════ */
.mx-stat{
background:var(--mx-s2);
border-radius:var(--mx-r);
padding:16px;
position:relative;overflow:hidden;
cursor:pointer;
box-shadow:var(--mx-sh1);
transition:all var(--mx-d3) var(--mx-ease);
}
.mx-stat:hover{transition-duration:var(--mx-d2)}
.mx-stat:active{transform:scale(0.97)}
/* 顶部 accent 线 */
.mx-stat::before{
content:'';position:absolute;top:0;left:0;right:0;height:2px;
background:var(--mx-stat-accent,var(--mx-primary));
opacity:0;transition:opacity var(--mx-d2);
}
.mx-stat:hover::before{opacity:1}
/* 右上角柔光晕 */
.mx-stat::after{
content:'';position:absolute;top:-40%;right:-20%;
width:100px;height:100px;
background:var(--mx-stat-accent,var(--mx-primary));
border-radius:50%;opacity:0.04;
transition:opacity var(--mx-d2);
}
.mx-stat:hover::after{opacity:0.08}
.mx-stat-label{
font-size:11px;font-weight:600;color:var(--mx-t3);
text-transform:uppercase;letter-spacing:0.06em;
margin-bottom:10px;display:flex;align-items:center;gap:8px;
}
.mx-stat-icon{
width:26px;height:26px;border-radius:var(--mx-r-xs);
display:flex;align-items:center;justify-content:center;
transition:transform var(--mx-d2) var(--mx-ease);
}
.mx-stat:hover .mx-stat-icon{transform:scale(1.12) rotate(-3deg)}
.mx-stat-value{
font-size:28px;font-weight:800;
letter-spacing:-0.03em;
font-variant-numeric:tabular-nums;
line-height:1;margin-bottom:8px;
}
.mx-stat-delta{
display:inline-flex;align-items:center;gap:4px;
font-size:11px;font-weight:600;
padding:2px 8px;border-radius:var(--mx-r-pill);
}
.mx-stat-delta.up{background:rgba(52,211,153,0.1);color:var(--mx-green)}
.mx-stat-delta.down{background:rgba(248,113,113,0.1);color:var(--mx-red)}
/* Grid layout */
.mx-stat-grid{
display:grid;grid-template-columns:repeat(2,1fr);gap:12px;
}
@media(min-width:768px){
.mx-stat-grid{grid-template-columns:repeat(4,1fr)}
}
/* Mobile */
@media(max-width:767px){
.mx-stat{padding:14px}
.mx-stat-value{font-size:22px}
.mx-stat-icon{width:24px;height:24px}
.mx-stat-icon svg{width:12px;height:12px}
.mx-stat-label{font-size:10px}
.mx-stat-delta{font-size:10px;padding:2px 6px}
}
+67
View File
@@ -0,0 +1,67 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Switch Component
对应 Miuix Switch.kt — miuixCapsuleShape
═══════════════════════════════════════════ */
.mx-switch{
display:inline-flex;align-items:center;gap:10px;
cursor:pointer;
-webkit-tap-highlight-color:transparent;
}
.mx-switch input{display:none}
.mx-switch-track{
width:44px;height:26px;
background:var(--mx-s4);
border-radius:var(--mx-r-pill);
position:relative;
transition:background var(--mx-d2) var(--mx-ease);
flex-shrink:0;
}
.mx-switch-thumb{
position:absolute;top:3px;left:3px;
width:20px;height:20px;
background:#fff;
border-radius:50%;
box-shadow:0 1px 4px rgba(0,0,0,0.25);
transition:transform var(--mx-d2) var(--mx-spring);
}
@keyframes mx-switch-bounce{
0%{transform:translateX(0)}
50%{transform:translateX(22px)}
100%{transform:translateX(20px)}
}
/* Checked */
.mx-switch input:checked + .mx-switch-track{
background:var(--mx-primary);
}
.mx-switch input:checked + .mx-switch-track .mx-switch-thumb{
animation:mx-switch-bounce 0.3s cubic-bezier(0.34,1.56,0.64,1) forwards;
}
/* Disabled */
.mx-switch input:disabled + .mx-switch-track{
opacity:0.4;cursor:not-allowed;
}
/* Label */
.mx-switch-label{
font-size:14px;color:var(--mx-text);
transition:color var(--mx-d1);
}
/* Mobile: 加大触控区 */
@media(max-width:767px){
.mx-switch{min-height:44px;padding:4px 0}
.mx-switch-track{width:48px;height:28px}
.mx-switch-thumb{width:22px;height:22px;top:3px;left:3px}
@keyframes mx-switch-bounce-mobile{
0%{transform:translateX(0)}
50%{transform:translateX(22px)}
100%{transform:translateX(20px)}
}
.mx-switch input:checked + .mx-switch-track .mx-switch-thumb{animation:mx-switch-bounce-mobile 0.3s cubic-bezier(0.34,1.56,0.64,1) forwards}
.mx-switch-label{font-size:15px}
}
+85
View File
@@ -0,0 +1,85 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Table Component
═══════════════════════════════════════════ */
.mx-table-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
.mx-table{width:100%;border-collapse:collapse}
.mx-table th{
padding:12px 16px;
font-size:11px;font-weight:700;
color:var(--mx-t3);text-align:left;
text-transform:uppercase;letter-spacing:0.06em;
border-bottom:1px solid var(--mx-divider);
white-space:nowrap;cursor:pointer;
position:relative;transition:color var(--mx-d1);
-webkit-user-select:none;user-select:none;
}
.mx-table th:hover{color:var(--mx-t2)}
/* Miuix: 表头 hover 底部亮线 */
.mx-table th::after{
content:'';position:absolute;bottom:0;left:14px;right:14px;
height:2px;background:var(--mx-primary);border-radius:1px;
transform:scaleX(0);transition:transform var(--mx-d2) var(--mx-ease);
}
.mx-table th:hover::after{transform:scaleX(1)}
.mx-table td{
padding:12px 16px;
font-size:13px;
border-bottom:1px solid var(--mx-divider);
white-space:nowrap;
transition:padding var(--mx-d1);
}
.mx-table tbody tr{
transition:background var(--mx-d3);
}
.mx-table tbody tr:hover{
background:var(--mx-s-hover);
transition-duration:var(--mx-d2);
}
.mx-table tbody tr:active{background:var(--mx-s-active)}
/* Miuix: hover 时左 padding 微推 */
.mx-table tbody tr:hover td{padding-left:18px}
/* Mono cell */
.mx-table .mono{
font-family:var(--mx-mono);font-size:12px;color:var(--mx-t2);
}
/* Compact mode */
.mx-table-compact th{padding:8px 12px}
.mx-table-compact td{padding:10px 12px}
.mx-table-compact tbody tr:hover td{padding-left:14px}
/* Mobile: 表格卡片化(需 data-label 属性配合) */
@media(max-width:767px){
.mx-table-wrap{overflow-x:visible}
.mx-table{border:0}
.mx-table thead{display:none}
.mx-table tbody tr{
display:block;margin-bottom:12px;
border:1px solid var(--mx-divider);
border-radius:var(--mx-r-sm);
padding:12px;
background:var(--mx-s2);
}
.mx-table tbody td{
display:flex;justify-content:space-between;align-items:center;
padding:6px 0;border-bottom:1px solid var(--mx-divider);
white-space:normal;
}
.mx-table tbody td:last-child{border-bottom:none}
.mx-table tbody td::before{
content:attr(data-label);
font-size:11px;font-weight:700;color:var(--mx-t3);
text-transform:uppercase;letter-spacing:0.04em;
flex-shrink:0;margin-right:12px;
}
.mx-table tbody tr:hover td{padding-left:0}
.mx-table tbody tr:active{background:var(--mx-s-hover)}
.mx-table-compact tbody tr{padding:10px}
.mx-table-compact td{padding:5px 0}
}
+94
View File
@@ -0,0 +1,94 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Tabs Component
═══════════════════════════════════════════ */
/* Tab bar */
.mx-tabs{
display:flex;align-items:center;
border-bottom:1px solid var(--mx-divider);
overflow-x:auto;scrollbar-width:none;
-webkit-overflow-scrolling:touch;
}
.mx-tabs::-webkit-scrollbar{display:none}
/* Tab item */
.mx-tab{
display:flex;align-items:center;gap:6px;
padding:10px 16px;
font-size:13px;font-weight:500;
color:var(--mx-t3);
cursor:pointer;white-space:nowrap;
position:relative;
transition:all var(--mx-d1) var(--mx-ease);
-webkit-tap-highlight-color:transparent;
}
.mx-tab:hover{color:var(--mx-text)}
/* Active indicator — bottom line */
.mx-tab::after{
content:'';position:absolute;bottom:0;left:8px;right:8px;
height:2px;border-radius:1px;
background:var(--mx-primary);
transform:scaleX(0);
transition:transform var(--mx-d2) var(--mx-spring);
}
.mx-tab.active{color:var(--mx-primary);font-weight:600}
.mx-tab.active::after{transform:scaleX(1)}
/* Badge in tab */
.mx-tab .mx-count{margin-left:4px}
/* Disabled */
.mx-tab.disabled{opacity:0.4;pointer-events:none}
/* Pill variant */
.mx-tabs-pill{
border-bottom:none;
background:var(--mx-s3);
border-radius:var(--mx-r-pill);
padding:3px;gap:2px;
}
.mx-tabs-pill .mx-tab{
border-radius:var(--mx-r-pill);
padding:6px 14px;
}
.mx-tabs-pill .mx-tab::after{display:none}
.mx-tabs-pill .mx-tab.active{
background:var(--mx-primary);color:#fff;
box-shadow:0 2px 8px rgba(76,139,245,0.25);
}
/* Card variant */
.mx-tabs-card{
border-bottom:none;gap:4px;
}
.mx-tabs-card .mx-tab{
border-radius:var(--mx-r-sm);
padding:8px 14px;
}
.mx-tabs-card .mx-tab::after{display:none}
.mx-tabs-card .mx-tab.active{
background:var(--mx-primary-glow);color:var(--mx-primary);
}
/* Tab content */
.mx-tab-panel{display:none;padding:16px 0}
.mx-tab-panel.active{display:block}
/* Alignment */
.mx-tabs-center{justify-content:center}
.mx-tabs-end{justify-content:flex-end}
/* Mobile: 触控友好 + 可滚动 */
@media(max-width:767px){
.mx-tab{padding:12px 14px;min-height:44px;font-size:14px}
.mx-tabs-pill .mx-tab{padding:8px 16px}
.mx-tabs-card .mx-tab{padding:10px 14px}
/* 隐藏超出的 badge */
.mx-tab .mx-count{display:none}
}
/* Tablet: 可滚动 */
@media(min-width:768px) and (max-width:1023px){
.mx-tabs{overflow-x:auto;-webkit-overflow-scrolling:touch}
}
+94
View File
@@ -0,0 +1,94 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Tag / Chip Component
═══════════════════════════════════════════ */
.mx-tag{
display:inline-flex;align-items:center;gap:4px;
padding:4px 10px;
border-radius:var(--mx-r-pill);
font-size:12px;font-weight:600;
background:var(--mx-s3);color:var(--mx-text);
transition:all var(--mx-d1) var(--mx-ease);
cursor:default;
-webkit-tap-highlight-color:transparent;
}
/* Clickable */
.mx-tag-clickable{cursor:pointer}
.mx-tag-clickable:hover{background:var(--mx-s4)}
.mx-tag-clickable:active{transform:scale(0.95)}
/* Color variants */
.mx-tag-primary{background:var(--mx-primary-glow);color:var(--mx-primary)}
.mx-tag-success{background:rgba(52,211,153,0.1);color:var(--mx-green)}
.mx-tag-warning{background:rgba(251,191,36,0.1);color:var(--mx-amber)}
.mx-tag-error{background:rgba(248,113,113,0.1);color:var(--mx-red)}
.mx-tag-info{background:rgba(96,165,250,0.1);color:var(--mx-blue)}
/* Outline variant */
.mx-tag-outline{
background:transparent;
border:1px solid var(--mx-border);
}
.mx-tag-outline.mx-tag-primary{border-color:var(--mx-primary);color:var(--mx-primary)}
.mx-tag-outline.mx-tag-success{border-color:var(--mx-green);color:var(--mx-green)}
.mx-tag-outline.mx-tag-warning{border-color:var(--mx-amber);color:var(--mx-amber)}
.mx-tag-outline.mx-tag-error{border-color:var(--mx-red);color:var(--mx-red)}
/* With dot */
.mx-tag-dot::before{
content:'';width:6px;height:6px;
border-radius:50%;flex-shrink:0;
}
.mx-tag-primary.mx-tag-dot::before{background:var(--mx-primary)}
.mx-tag-success.mx-tag-dot::before{background:var(--mx-green)}
.mx-tag-warning.mx-tag-dot::before{background:var(--mx-amber)}
.mx-tag-error.mx-tag-dot::before{background:var(--mx-red)}
.mx-tag-info.mx-tag-dot::before{background:var(--mx-blue)}
/* Close button */
.mx-tag-close{
width:14px;height:14px;
display:flex;align-items:center;justify-content:center;
border-radius:50%;cursor:pointer;
transition:background var(--mx-d1);
}
.mx-tag-close:hover{background:rgba(0,0,0,0.1)}
[data-theme="dark"] .mx-tag-close:hover{background:rgba(255,255,255,0.1)}
/* Sizes */
.mx-tag-sm{padding:2px 8px;font-size:11px}
.mx-tag-lg{padding:6px 14px;font-size:13px}
/* Tag group */
.mx-tag-group{
display:flex;flex-wrap:wrap;gap:6px;
}
/* Input tag — for editable tag list */
.mx-tag-input{
display:flex;flex-wrap:wrap;gap:6px;
padding:6px 10px;
background:var(--mx-s3);
border:1px solid var(--mx-border);
border-radius:var(--mx-r-sm);
min-height:40px;align-items:center;
transition:all var(--mx-d2) var(--mx-ease);
cursor:text;
}
/* Mobile */
@media(max-width:767px){
.mx-tag{min-height:32px;padding:4px 12px}
.mx-tag-lg{min-height:38px}
.mx-tag-close{width:18px;height:18px}
}
.mx-tag-input:focus-within{
border-color:var(--mx-primary);
box-shadow:0 0 0 3px var(--mx-primary-glow);
}
.mx-tag-input input{
flex:1;min-width:60px;border:none;outline:none;
background:none;font-size:13px;color:var(--mx-text);
}
.mx-tag-input input::placeholder{color:var(--mx-t3)}
+98
View File
@@ -0,0 +1,98 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Toast / Snackbar Component
Miuix 轻量反馈 — 底部弹出 + 自动消失
═══════════════════════════════════════════ */
/* Container */
.mx-toast-container{
position:fixed;bottom:16px;left:50%;
transform:translateX(-50%);
z-index:2000;
display:flex;flex-direction:column-reverse;align-items:center;gap:8px;
pointer-events:none;
padding-bottom:var(--mx-safe-b);
}
/* Toast */
.mx-toast{
display:flex;align-items:center;gap:10px;
padding:10px 16px;
background:var(--mx-s4);
border-radius:var(--mx-r-sm);
box-shadow:var(--mx-sh3);
font-size:13px;font-weight:500;
color:var(--mx-text);
pointer-events:auto;
max-width:calc(100vw - 32px);
transform:translateY(12px) scale(0.95);
opacity:0;
transition:transform var(--mx-d3) var(--mx-spring),
opacity var(--mx-d2) var(--mx-ease);
}
.mx-toast.show{
transform:translateY(0) scale(1);
opacity:1;
}
.mx-toast.hide{
transform:translateY(-8px) scale(0.95);
opacity:0;
}
/* Icon */
.mx-toast-icon{
width:20px;height:20px;flex-shrink:0;
display:flex;align-items:center;justify-content:center;
}
.mx-toast-icon svg{width:18px;height:18px}
/* Variants */
.mx-toast-success .mx-toast-icon{color:var(--mx-green)}
.mx-toast-warning .mx-toast-icon{color:var(--mx-amber)}
.mx-toast-error .mx-toast-icon{color:var(--mx-red)}
.mx-toast-info .mx-toast-icon{color:var(--mx-blue)}
/* Action button */
.mx-toast-action{
padding:4px 10px;border-radius:var(--mx-r-xs);
font-size:12px;font-weight:600;
cursor:pointer;
transition:all var(--mx-d1) var(--mx-ease);
flex-shrink:0;
}
.mx-toast-action{color:var(--mx-primary)}
.mx-toast-action:hover{background:var(--mx-primary-glow)}
/* Close button */
.mx-toast-close{
width:20px;height:20px;
display:flex;align-items:center;justify-content:center;
border-radius:var(--mx-r-xs);
cursor:pointer;color:var(--mx-t3);
transition:all var(--mx-d1);
flex-shrink:0;
}
.mx-toast-close:hover{color:var(--mx-text)}
/* Progress bar (auto dismiss) */
.mx-toast-progress{
position:absolute;bottom:0;left:0;
height:2px;border-radius:0 0 var(--mx-r-sm) var(--mx-r-sm);
background:var(--mx-primary);
opacity:0.4;
transition:width linear;
}
/* Mobile — full width + 底部安全区 */
@media(max-width:767px){
.mx-toast-container{
left:8px;right:8px;
transform:none;
align-items:stretch;
}
.mx-toast{border-radius:var(--mx-r)}
}
/* Tablet */
@media(min-width:768px) and (max-width:1023px){
.mx-toast-container{bottom:20px}
}
+54
View File
@@ -0,0 +1,54 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Tooltip Component
═══════════════════════════════════════════ */
.mx-tooltip{
position:relative;
}
.mx-tooltip::after{
content:attr(data-tooltip);
position:absolute;
bottom:calc(100% + 8px);left:50%;
transform:translateX(-50%) translateY(4px);
padding:5px 10px;
background:var(--mx-s4);
color:var(--mx-text);
font-size:12px;font-weight:500;
white-space:nowrap;
border-radius:var(--mx-r-xs);
box-shadow:var(--mx-sh2);
opacity:0;visibility:hidden;
pointer-events:none;
transition:all var(--mx-d2) var(--mx-spring);
z-index:500;
}
.mx-tooltip:hover::after{
opacity:1;visibility:visible;
transform:translateX(-50%) translateY(0);
}
/* Position variants */
.mx-tooltip-bottom::after{
bottom:auto;top:calc(100% + 8px);
transform:translateX(-50%) translateY(-4px);
}
.mx-tooltip-bottom:hover::after{
transform:translateX(-50%) translateY(0);
}
.mx-tooltip-left::after{
bottom:auto;left:auto;
right:calc(100% + 8px);top:50%;
transform:translateY(-50%) translateX(4px);
}
.mx-tooltip-left:hover::after{
transform:translateY(-50%) translateX(0);
}
.mx-tooltip-right::after{
bottom:auto;left:calc(100% + 8px);top:50%;
transform:translateY(-50%) translateX(-4px);
}
.mx-tooltip-right:hover::after{
transform:translateY(-50%) translateX(0);
}
+71
View File
@@ -0,0 +1,71 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Animations
═══════════════════════════════════════════ */
/* ── Entry animations ── */
@keyframes mx-slide-up{
from{opacity:0;transform:translateY(16px)}
to{opacity:1;transform:translateY(0)}
}
@keyframes mx-card-in{
from{opacity:0;transform:scale(0.96) translateY(10px)}
to{opacity:1;transform:scale(1) translateY(0)}
}
@keyframes mx-fade-item{
from{opacity:0;transform:translateX(-8px)}
to{opacity:1;transform:translateX(0)}
}
@keyframes mx-fade-row{
from{opacity:0;transform:translateY(4px)}
to{opacity:1;transform:translateY(0)}
}
@keyframes mx-bar-grow{
from{transform:scaleY(0);opacity:0}
to{transform:scaleY(1);opacity:1}
}
@keyframes mx-pulse{
0%,100%{opacity:1;transform:scale(1)}
50%{opacity:0.5;transform:scale(1.3)}
}
@keyframes mx-dot-in{
from{transform:translateX(-50%) scale(0);opacity:0}
to{transform:translateX(-50%) scale(1);opacity:1}
}
@keyframes mx-ripple{
to{transform:scale(4);opacity:0}
}
@keyframes mx-shimmer{
from{background-position:200% 0}
to{background-position:-200% 0}
}
/* ── Animation classes ── */
.mx-anim-slide-up{animation:mx-slide-up var(--mx-d3) var(--mx-ease)}
.mx-anim-card-in{animation:mx-card-in var(--mx-d3) var(--mx-ease) both}
.mx-anim-fade-item{animation:mx-fade-item var(--mx-d3) var(--mx-ease) both}
.mx-anim-fade-row{animation:mx-fade-row var(--mx-d3) var(--mx-ease) both}
.mx-anim-bar-grow{
transform-origin:bottom;
animation:mx-bar-grow var(--mx-d4) var(--mx-ease) both;
}
/* ── Stagger delays ── */
.mx-delay-0{animation-delay:0ms}
.mx-delay-1{animation-delay:50ms}
.mx-delay-2{animation-delay:100ms}
.mx-delay-3{animation-delay:150ms}
.mx-delay-4{animation-delay:200ms}
.mx-delay-5{animation-delay:250ms}
.mx-delay-6{animation-delay:300ms}
.mx-delay-7{animation-delay:350ms}
.mx-delay-8{animation-delay:400ms}
/* ── Skeleton loading — dual-layer overlay ── */
.mx-skeleton{
background:
linear-gradient(90deg, transparent 25%, rgba(255,255,255,0.06) 50%, transparent 75%),
var(--mx-s3);
background-size:200% 100%, 100% 100%;
animation:mx-shimmer 1.5s ease-in-out infinite;
border-radius:var(--mx-r-sm);
}
+50
View File
@@ -0,0 +1,50 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Base Reset & Globals
═══════════════════════════════════════════ */
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
html{font-size:14px;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
body{
font-family:var(--mx-font);
background:var(--mx-bg);
color:var(--mx-text);
overflow-x:hidden;
min-height:100dvh;
}
button,input,textarea,select{font-family:inherit;border:none;outline:none;background:none}
ul,ol{list-style:none}
a{text-decoration:none;color:inherit}
img{display:block;max-width:100%}
/* Smooth scroll */
html{scroll-behavior:smooth}
/* Selection */
::selection{background:var(--mx-primary-glow);color:var(--mx-text)}
/* Scrollbar — Miuix thin style, auto-hide */
::-webkit-scrollbar{width:4px;height:4px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:var(--mx-s4);border-radius:2px;opacity:0;transition:opacity 0.3s}
::-webkit-scrollbar-thumb:hover{background:var(--mx-t3)}
body:hover ::-webkit-scrollbar-thumb{opacity:1}
/* Firefox scrollbar auto-hide */
*{scrollbar-color:transparent transparent}
body:hover *{scrollbar-color:var(--mx-s4) transparent}
/* Prevent selection on interactive */
.no-select{-webkit-user-select:none;user-select:none}
/* ═══ DARK MODE INNER GLOW — Miuix signature depth ═══ */
[data-theme="dark"] .mx-card,
[data-theme="dark"] .mx-stat{
box-shadow:var(--mx-sh1),inset 0 1px 0 rgba(255,255,255,0.04);
}
/* ═══ prefers-reduced-motion — kill all animation ═══ */
@media(prefers-reduced-motion:reduce){
*,*::before,*::after{
animation-duration:0.01ms !important;
transition-duration:0.01ms !important;
}
}
+230
View File
@@ -0,0 +1,230 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Liquid Glass Effect v2
液体玻璃效果系统(完整版)
通过 [data-glass="liquid"] 属性启用
═══════════════════════════════════════════ */
/* ── Glass Variables ── */
:root {
--mx-glass-blur: 12px;
--mx-glass-saturation: 180%;
--mx-glass-displacement: 25;
--mx-glass-aberration: 2;
--mx-glass-elasticity: 0.15;
--mx-glass-corner-radius: 16px;
--mx-glass-activation-zone: 200;
--mx-glass-bg-light: rgba(255, 255, 255, 0.25);
--mx-glass-bg-dark: rgba(0, 0, 0, 0.35);
--mx-glass-border-light: rgba(255, 255, 255, 0.2);
--mx-glass-border-dark: rgba(255, 255, 255, 0.08);
--mx-glass-mouse-x: 0;
--mx-glass-mouse-y: 0;
}
/* ═══════════════════════════════════════════
DARK MODE (default)
═══════════════════════════════════════════ */
/* ── TopBar ── */
[data-glass="liquid"] .mx-topbar {
background: rgba(22, 22, 22, 0.45);
backdrop-filter: blur(var(--mx-glass-blur)) saturate(var(--mx-glass-saturation));
-webkit-backdrop-filter: blur(var(--mx-glass-blur)) saturate(var(--mx-glass-saturation));
border-bottom: 1px solid var(--mx-glass-border-dark);
filter: url(#mx-glass-filter);
}
/* ── Sidebar ── */
[data-glass="liquid"] .mx-sidebar {
background: rgba(22, 22, 22, 0.45);
backdrop-filter: blur(var(--mx-glass-blur)) saturate(var(--mx-glass-saturation));
-webkit-backdrop-filter: blur(var(--mx-glass-blur)) saturate(var(--mx-glass-saturation));
border-right: 1px solid var(--mx-glass-border-dark);
filter: url(#mx-glass-filter);
}
/* ── Bottom Nav ── */
[data-glass="liquid"] .mx-bottom-nav {
background: rgba(22, 22, 22, 0.45);
backdrop-filter: blur(var(--mx-glass-blur)) saturate(var(--mx-glass-saturation));
-webkit-backdrop-filter: blur(var(--mx-glass-blur)) saturate(var(--mx-glass-saturation));
border-top: 1px solid var(--mx-glass-border-dark);
filter: url(#mx-glass-filter);
}
/* ── Modal ── */
[data-glass="liquid"] .mx-modal {
background: rgba(30, 30, 30, 0.5);
backdrop-filter: blur(var(--mx-glass-blur)) saturate(var(--mx-glass-saturation));
-webkit-backdrop-filter: blur(var(--mx-glass-blur)) saturate(var(--mx-glass-saturation));
border: 1px solid var(--mx-glass-border-dark);
filter: url(#mx-glass-filter);
}
/* ── Drawer ── */
[data-glass="liquid"] .mx-drawer {
background: rgba(22, 22, 22, 0.5);
backdrop-filter: blur(var(--mx-glass-blur)) saturate(var(--mx-glass-saturation));
-webkit-backdrop-filter: blur(var(--mx-glass-blur)) saturate(var(--mx-glass-saturation));
border-left: 1px solid var(--mx-glass-border-dark);
filter: url(#mx-glass-filter);
}
/* ── Popover ── */
[data-glass="liquid"] .mx-popover {
background: rgba(30, 30, 30, 0.5);
backdrop-filter: blur(var(--mx-glass-blur)) saturate(var(--mx-glass-saturation));
-webkit-backdrop-filter: blur(var(--mx-glass-blur)) saturate(var(--mx-glass-saturation));
border: 1px solid var(--mx-glass-border-dark);
filter: url(#mx-glass-filter);
}
/* ── Select Dropdown ── */
[data-glass="liquid"] .mx-select-dropdown {
background: rgba(30, 30, 30, 0.5);
backdrop-filter: blur(var(--mx-glass-blur)) saturate(var(--mx-glass-saturation));
-webkit-backdrop-filter: blur(var(--mx-glass-blur)) saturate(var(--mx-glass-saturation));
border: 1px solid var(--mx-glass-border-dark);
filter: url(#mx-glass-filter);
}
/* ── Card (opt-in via .mx-card-glass) ── */
[data-glass="liquid"] .mx-card-glass {
background: rgba(30, 30, 30, 0.4);
backdrop-filter: blur(var(--mx-glass-blur)) saturate(var(--mx-glass-saturation));
-webkit-backdrop-filter: blur(var(--mx-glass-blur)) saturate(var(--mx-glass-saturation));
border: 1px solid var(--mx-glass-border-dark);
filter: url(#mx-glass-filter);
}
/* ═══════════════════════════════════════════
LIGHT MODE
═══════════════════════════════════════════ */
[data-theme="light"][data-glass="liquid"] .mx-topbar {
background: rgba(255, 255, 255, 0.45);
border-bottom: 1px solid var(--mx-glass-border-light);
}
[data-theme="light"][data-glass="liquid"] .mx-sidebar {
background: rgba(255, 255, 255, 0.45);
border-right: 1px solid var(--mx-glass-border-light);
}
[data-theme="light"][data-glass="liquid"] .mx-bottom-nav {
background: rgba(255, 255, 255, 0.45);
border-top: 1px solid var(--mx-glass-border-light);
}
[data-theme="light"][data-glass="liquid"] .mx-modal {
background: rgba(255, 255, 255, 0.5);
border: 1px solid var(--mx-glass-border-light);
}
[data-theme="light"][data-glass="liquid"] .mx-drawer {
background: rgba(255, 255, 255, 0.5);
border-left: 1px solid var(--mx-glass-border-light);
}
[data-theme="light"][data-glass="liquid"] .mx-popover {
background: rgba(255, 255, 255, 0.5);
border: 1px solid var(--mx-glass-border-light);
}
[data-theme="light"][data-glass="liquid"] .mx-select-dropdown {
background: rgba(255, 255, 255, 0.5);
border: 1px solid var(--mx-glass-border-light);
}
[data-theme="light"][data-glass="liquid"] .mx-card-glass {
background: rgba(255, 255, 255, 0.4);
border: 1px solid var(--mx-glass-border-light);
}
/* ═══════════════════════════════════════════
OVER-LIGHT 模式
亮色背景上使用的深色玻璃
═══════════════════════════════════════════ */
[data-glass="liquid"][data-glass-over-light="true"] .mx-topbar,
[data-glass="liquid"][data-glass-over-light="true"] .mx-sidebar,
[data-glass="liquid"][data-glass-over-light="true"] .mx-bottom-nav,
[data-glass="liquid"][data-glass-over-light="true"] .mx-modal,
[data-glass="liquid"][data-glass-over-light="true"] .mx-drawer,
[data-glass="liquid"][data-glass-over-light="true"] .mx-popover,
[data-glass="liquid"][data-glass-over-light="true"] .mx-select-dropdown,
[data-glass="liquid"][data-glass-over-light="true"] .mx-card-glass {
background: rgba(0, 0, 0, 0.45);
border-color: rgba(255, 255, 255, 0.1);
}
/* ═══════════════════════════════════════════
GLASS 可追踪元素
鼠标跟踪 + 弹性变换 + 双层边框
═══════════════════════════════════════════ */
.mx-glass-trackable {
position: relative;
transition: transform 0.2s ease-out;
will-change: transform;
}
.mx-glass-trackable .mx-glass-border-layer {
position: absolute;
inset: 0;
pointer-events: none;
border-radius: inherit;
z-index: 2;
}
/* Hover 高光层 */
.mx-glass-trackable::after {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
pointer-events: none;
opacity: 0;
transition: opacity 0.2s ease-out;
background-image: radial-gradient(circle at 50% 0%, rgba(255,255,255,0.5) 0%, rgba(255,255,255,0) 50%);
mix-blend-mode: overlay;
z-index: 3;
}
.mx-glass-trackable:hover::after {
opacity: 0.5;
}
.mx-glass-trackable:active::after {
opacity: 0.8;
background-image: radial-gradient(circle at 50% 0%, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 80%);
}
/* ═══════════════════════════════════════════
FALLBACK — browsers without SVG filter support
═══════════════════════════════════════════ */
@supports not (filter: url(#mx-glass-filter)) {
[data-glass="liquid"] .mx-topbar,
[data-glass="liquid"] .mx-sidebar,
[data-glass="liquid"] .mx-bottom-nav,
[data-glass="liquid"] .mx-modal,
[data-glass="liquid"] .mx-drawer,
[data-glass="liquid"] .mx-popover,
[data-glass="liquid"] .mx-select-dropdown,
[data-glass="liquid"] .mx-card-glass {
filter: none;
}
}
/* ═══════════════════════════════════════════
REDUCED MOTION
═══════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
.mx-glass-trackable {
transition: none !important;
transform: none !important;
}
.mx-glass-trackable::after {
transition: none !important;
}
}
+198
View File
@@ -0,0 +1,198 @@
/* ═══════════════════════════════════════════
MIUIX CONSOLE — Design Tokens
直接从 Miuix Colors.kt lightColorScheme() / darkColorScheme() 移植
═══════════════════════════════════════════ */
:root {
/* ── Primary — from Miuix lightColorScheme primary = Color(0xFF3482FF) ── */
--mx-primary: #3482FF;
--mx-primary-hover: #5D9BFF;
--mx-primary-active: #277AF7;
--mx-primary-on: #FFFFFF;
--mx-primary-glow: rgba(52,130,255,0.12);
--mx-primary-container: #5D9BFF;
--mx-primary-container-on: #FFFFFF;
--mx-primary-variant: #3482FF;
--mx-primary-variant-on: #AECDFF;
--mx-tertiary-container: #EAF2FF;
--mx-tertiary-container-on: #3482FF;
/* ── Error — from Miuix error = Color(0xFFE94634) ── */
--mx-error: #E94634;
--mx-error-on: #FFFFFF;
--mx-error-container: #FDF6F4;
--mx-error-container-on: #410002;
/* ── Semantic ── */
--mx-green: #34D399;
--mx-amber: #FBBF24;
--mx-blue: #60A5FA;
/* ── Surface — from Miuix lightColorScheme() 源码 ── */
--mx-bg: #FFFFFF; /* background = White */
--mx-surface: #F7F7F7; /* surface = 0xFFF7F7F7 */
--mx-surface-variant: #FFFFFF; /* surfaceVariant = White */
--mx-s1: #F7F7F7; /* surface */
--mx-s2: #FFFFFF; /* surfaceContainer = White */
--mx-s3: #E8E8E8; /* surfaceContainerHigh = 0xFFE8E8E8 */
--mx-s4: #E8E8E8; /* surfaceContainerHighest = 0xFFE8E8E8 */
--mx-s-hover: rgba(0,0,0,0.04);
--mx-s-active: rgba(0,0,0,0.07);
/* ── Text — from Miuix 源码 ── */
--mx-text: #000000; /* onSurface = Black */
--mx-text-on-surface: rgba(0,0,0,0.8); /* onSurfaceSecondary = 0xCC000000 */
--mx-text-variant: rgba(0,0,0,0.6); /* onSurfaceVariantSummary = 0x99000000 */
--mx-text-actions: rgba(0,0,0,0.4); /* onSurfaceVariantActions = 0x66000000 */
--mx-t2: rgba(0,0,0,0.6);
--mx-t3: rgba(0,0,0,0.4);
--mx-text-bg-variant: #8C93B0; /* onBackgroundVariant */
--mx-text-container-variant: #959595; /* onSurfaceContainerVariant */
--mx-text-container-high: #A2A2A2; /* onSurfaceContainerHigh */
--mx-text-disabled: #B2B2B2; /* disabledOnSurface */
/* ── Secondary — from Miuix 源码 ── */
--mx-secondary: #E6E6E6; /* secondary */
--mx-secondary-variant: #F0F0F0; /* secondaryVariant */
--mx-secondary-container: #F0F0F0;
--mx-secondary-container-on: #A9A9A9;
/* ── Border / Divider — from Miuix 源码 ── */
--mx-border: #D9D9D9; /* outline = 0xFFD9D9D9 */
--mx-divider: #E0E0E0; /* dividerLine = 0xFFE0E0E0 */
/* ── Disabled — from Miuix 源码 ── */
--mx-disabled-primary: #C2D9FF;
--mx-disabled-primary-on: #F3F8FF;
--mx-disabled-secondary: #F0F0F0;
--mx-disabled-secondary-on: #FCFCFC;
/* ── Window ── */
--mx-window-dim: rgba(0,0,0,0.3); /* windowDimming */
/* ── Slider — from Miuix 源码 ── */
--mx-slider-bg: rgba(0,0,0,0.06); /* 0x0F000000 */
--mx-slider-key: rgba(163,179,205,0.3);
--mx-slider-key-fg: #6EB5FF;
/* ── Radius (Miuix Smooth Corner — 16dp default) ── */
--mx-r: 16px;
--mx-r-sm: 12px;
--mx-r-xs: 8px;
--mx-r-pill: 9999px;
/* ── Shadow (Miuix multi-layer soft shadow) ── */
--mx-sh1: 0 1px 4px rgba(0,0,0,0.08);
--mx-sh2: 0 4px 16px rgba(0,0,0,0.06);
--mx-sh3: 0 8px 32px rgba(0,0,0,0.08);
--mx-glow: 0 0 0 1px rgba(52,130,255,0.06), 0 2px 12px rgba(52,130,255,0.04);
/* ── Motion (Miuix spring + ease) ── */
--mx-ease: cubic-bezier(0.16, 1, 0.3, 1);
--mx-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--mx-d1: 100ms;
--mx-d2: 200ms;
--mx-d3: 320ms;
--mx-d4: 500ms;
/* ── Typography ── */
--mx-font: "MiSans", "Mi Sans", -apple-system, "PingFang SC", "Noto Sans SC", sans-serif;
--mx-mono: "JetBrains Mono", "SF Mono", monospace;
/* Typography scale */
--mx-text-xs: 11px;
--mx-text-sm: 12px;
--mx-text-base: 13px;
--mx-text-md: 14px;
--mx-text-lg: 15px;
--mx-text-xl: 16px;
--mx-text-2xl: 20px;
--mx-text-3xl: 24px;
/* ── Safe area ── */
--mx-safe-t: env(safe-area-inset-top, 0px);
--mx-safe-b: env(safe-area-inset-bottom, 0px);
}
/* ═══════════════════════════════════════════
DARK MODE — from Miuix darkColorScheme() 源码
═══════════════════════════════════════════ */
[data-theme="dark"] {
/* Primary — darkColorScheme primary = Color(0xFF277AF7) */
--mx-primary: #277AF7;
--mx-primary-hover: #338FE4;
--mx-primary-active: #0073DD;
--mx-primary-on: #FFFFFF;
--mx-primary-glow: rgba(39,122,247,0.15);
--mx-primary-container: #338FE4;
--mx-primary-container-on: #FFFFFF;
--mx-primary-variant: #0073DD;
--mx-primary-variant-on: #99C7F1;
--mx-tertiary-container: #2B3B54;
--mx-tertiary-container-on: #4788FF;
/* Error — darkColorScheme error = Color(0xFFF12522) */
--mx-error: #F12522;
--mx-error-on: #FFFFFF;
--mx-error-container: #2E0603;
--mx-error-container-on: #FFDAD6;
/* Surface — 5-level gradient with 3-5% brightness steps */
--mx-bg: #161616; /* 页面背景 — 最底层 */
--mx-surface: #0C0C0C; /* surface = s1 */
--mx-surface-variant: #1C1C1C; /* surfaceVariant = s2 */
--mx-s1: #0C0C0C; /* 导航栏、侧边栏 — 比 bg 深 */
--mx-s2: #1C1C1C; /* 卡片 — 比 bg 浅 */
--mx-s3: #242424; /* 输入框、浮层 — 比 s2 浅 */
--mx-s4: #2C2C2C; /* 下拉菜单 — 最亮 */
--mx-s-hover: rgba(255,255,255,0.04);
--mx-s-active: rgba(255,255,255,0.07);
/* Text — from Miuix darkColorScheme() 源码 */
--mx-text: #F2F2F2; /* onSurface = 0xFFF2F2F2 */
--mx-text-on-surface: rgba(255,255,255,0.8); /* 0xCCFFFFFF */
--mx-text-variant: rgba(255,255,255,0.5); /* 0x80FFFFFF */
--mx-text-actions: rgba(255,255,255,0.4); /* 0x66FFFFFF */
--mx-t2: rgba(255,255,255,0.6);
--mx-t3: rgba(255,255,255,0.4);
--mx-text-bg-variant: #787E96;
--mx-text-container-variant: #737373;
--mx-text-container-high: #666666;
--mx-text-disabled: #666666;
/* Secondary — from Miuix darkColorScheme() 源码 */
--mx-secondary: #505050;
--mx-secondary-variant: #434343;
--mx-secondary-container: #434343;
--mx-secondary-container-on: #7C7C7C;
/* Border / Divider — 提升对比度 */
--mx-border: #484848; /* outline = 提升到可见 */
--mx-divider: #404040; /* dividerLine = 提升到可见 */
/* Disabled */
--mx-disabled-primary: #253E64;
--mx-disabled-primary-on: #677993;
--mx-disabled-secondary: #3F3F3F;
--mx-disabled-secondary-on: #797979;
/* Window */
--mx-window-dim: rgba(0,0,0,0.6);
/* Slider */
--mx-slider-bg: rgba(255,255,255,0.15);
--mx-slider-key: rgba(122,138,166,0.3);
--mx-slider-key-fg: #5DAAFF;
/* Shadow — dark mode 阴影更重 */
--mx-sh1: 0 1px 4px rgba(0,0,0,0.5);
--mx-sh2: 0 4px 16px rgba(0,0,0,0.4);
--mx-sh3: 0 8px 32px rgba(0,0,0,0.45);
--mx-glow: 0 0 0 1px rgba(39,122,247,0.08), 0 2px 12px rgba(39,122,247,0.06);
}
/* ═══ COMPACT DENSITY ═══ */
[data-density="compact"] {
--mx-r: 14px;
--mx-r-sm: 10px;
}