Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a0eb14b28
|
||
|
|
ee22a82ecd
|
||
|
|
e3acb37d32
|
||
|
|
d9a27b69f3
|
||
|
|
896b21fe44
|
||
|
|
cc6b82d7ec
|
||
|
|
4794bed2cf | ||
|
|
e6070f91ee | ||
|
|
26731958ea | ||
|
|
de7263569c | ||
|
|
c9ce7b64ef | ||
|
|
f3b09ca9a6 | ||
|
|
1cccc1aa00 | ||
|
|
f0538fa233 | ||
|
|
47ef7c63dd | ||
|
|
b74c6654e5 | ||
|
|
ac3d639d6b | ||
|
|
2ed16ff88d | ||
|
|
6f83ebbe3c | ||
|
|
4667d94c45 | ||
|
|
76b96cc374 | ||
|
|
03f7279b53 |
+4
-11
@@ -12,14 +12,7 @@ build-std = ["std", "panic_abort"]
|
||||
[env]
|
||||
MCU = "esp32s3"
|
||||
|
||||
# Use the ESP-IDF installation and toolchains that are already on the machine
|
||||
# (eim's install under ~/.espressif) instead of downloading another copy into
|
||||
# ./.embuild. Without this the first build re-downloads several hundred MB of
|
||||
# Xtensa GCC/LLVM.
|
||||
#
|
||||
# `global` means ~/.espressif, which is where eim installs.
|
||||
#
|
||||
# Drop these two lines to let esp-idf-sys clone and fetch the ESP-IDF version it
|
||||
# pins itself (v5.5.5 by default), into ~/.espressif.
|
||||
IDF_PATH = { value = "/Users/peng/.espressif/esp-idf/v5.5.5", force = true }
|
||||
ESP_IDF_TOOLS_INSTALL_DIR = { value = "global", force = true }
|
||||
# esp-idf-sys downloads this version unless IDF_PATH points to an existing SDK.
|
||||
# Keep machine-specific paths in the environment instead of this shared file.
|
||||
ESP_IDF_VERSION = { value = "v5.5.5", force = false }
|
||||
ESP_IDF_TOOLS_INSTALL_DIR = { value = "global", force = false }
|
||||
|
||||
@@ -20,6 +20,54 @@ WIFI_PASS=your-password
|
||||
#CAM_RES=vga
|
||||
# JPEG quality 0-63, lower is better quality
|
||||
#CAM_QUALITY=12
|
||||
# Optional adaptive compression ceiling, 0 = fixed (default), else QUALITY..63.
|
||||
# Shed load on sender congestion, restore detail after 15 stable seconds.
|
||||
#CAM_QUALITY_MAX=0
|
||||
# Stream cap, 0 = uncapped (default), 1-60 = maximum fps.
|
||||
# Paces frame retrieval and UDP delivery; sensor clocks remain unchanged.
|
||||
#CAM_MAX_FPS=0
|
||||
# OV5640 PLL profile: default (driver timing) | vga30 (VGA ~30.09 fps).
|
||||
# vga30 preserves HTS/VTS: XCLK 20, VCO 610, SYSCLK 61, PCLK 15.25 MHz.
|
||||
# Retune exposure-line limits when switching profiles; keep HTS at default.
|
||||
#CAM_CLOCK_PROFILE=default
|
||||
# Capture frame buffers in PSRAM: 3 (default) or 2 (lower capture queue depth).
|
||||
# Prefer 3 for burst tolerance; 2 showed worse long-run peak frame loss here.
|
||||
#CAM_FB_COUNT=3
|
||||
# Sensor grayscale effect while retaining hardware JPEG, 0 = colour (default).
|
||||
#CAM_GRAYSCALE=0
|
||||
# OV5640 AE target bias: 0 (default), -1 (~-1.1 EV), -2 (~-1.9 EV).
|
||||
# This target alone does not guarantee a shorter shutter.
|
||||
#CAM_EXPOSURE_EV=0
|
||||
# Optional OV5640 driver AE level -5..5; requires CAM_EXPOSURE_EV=0.
|
||||
# Unset uses the EV setting. -1 lowers the target 55 -> 45 (~-0.29 EV),
|
||||
# a finer brightness adjustment than CAM_EXPOSURE_EV=-1. AEC/AGC stay on.
|
||||
#CAM_AE_LEVEL=-1
|
||||
# OV5640 fixed shutter in sensor lines, 0 = automatic (default), AGC stays on.
|
||||
# 220 lines is roughly 10 ms with default VGA HTS 2060; verify timing/logs.
|
||||
#CAM_SHUTTER_LINES=0
|
||||
# Optional automatic exposure cap in sensor lines, 0 = driver default.
|
||||
# AEC/AGC stay automatic, night frame extension is disabled. Mutually exclusive
|
||||
# with a nonzero fixed shutter. Default VGA: 440 lines is about 20 ms.
|
||||
# With vga30 use 592 lines for about 20 ms, including two 50 Hz AEC bands.
|
||||
#CAM_EXPOSURE_MAX_LINES=0
|
||||
# OV5640 automatic gain ceiling, 0 = driver default, else 1..63 times.
|
||||
# More gain can brighten short exposures but also amplifies noise.
|
||||
#CAM_GAIN_CEILING=0
|
||||
# OV5640 horizontal total, 0 = driver default. Even values only; hardware test
|
||||
# required. Changing HTS also changes the time represented by one shutter line.
|
||||
# HTS 1500 / 1800 caused green/magenta flicker on this module; keep 0.
|
||||
#CAM_HTS=0
|
||||
# Minimum gap between successful video datagrams, 0 = burst (default).
|
||||
# Allowed 0..5000 us in FreeRTOS tick increments (1000 us in this build).
|
||||
#UDP_PACKET_GAP_US=0
|
||||
# Total yielding-retry allowance per JPEG, shared by all fragments/peers.
|
||||
# 0 = fail on backpressure; default 8000 us, 0..25000 in tick increments.
|
||||
# Frame deadlines remain 25 ms sending / 100 ms since capture start.
|
||||
#UDP_RETRY_BUDGET_US=8000
|
||||
# Internal ESP chip temperature load limit, Celsius; 0 disables, default 75.
|
||||
# At the limit halve fps (rounded up); restore after cooling by 5 C.
|
||||
# This is a proactive load threshold, not a specified silicon safety limit.
|
||||
#CAM_THERMAL_LIMIT_C=75
|
||||
# Set to 0 if your board inverts VSYNC in hardware.
|
||||
#CAM_VSYNC_INVERT=1
|
||||
|
||||
@@ -28,3 +76,13 @@ WIFI_PASS=your-password
|
||||
# the AP (a hidden SSID, for instance) - otherwise the scan reports the real
|
||||
# mode and this only makes it fail faster.
|
||||
#WIFI_AUTH=wpa2
|
||||
# Optional maximum Wi-Fi TX power, integer dBm (2..20). Unset keeps SDK default.
|
||||
# Test on your own board/network; higher power does not always improve video.
|
||||
# Current delivery requests 15 dBm; this ESP32-S3 reports a 15.00 dBm cap.
|
||||
# Runtime load control does not lower it; reconnects reapply the same cap.
|
||||
# WIFI_TX_POWER_DBM=15
|
||||
|
||||
# Diagnostic builds only: intentionally disconnect once, after N seconds of
|
||||
# the streaming loop, hold offline 5 seconds, then exercise runtime recovery.
|
||||
# Keep 0 (default) for normal firmware; does not change router/TUN settings.
|
||||
#WIFI_TEST_DROP_AFTER_S=0
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# actionlint 配置。
|
||||
#
|
||||
# 这个仓库的 CI 跑在 Gitea 上,但 actionlint 用的是 GitHub 的规则,所以有一条
|
||||
# 政策性的误报要忽略掉:
|
||||
#
|
||||
# the runner of "actions/upload-artifact@v3" action is too old to run on GitHub Actions
|
||||
#
|
||||
# 它对 GitHub 是成立的(v3 声明的 node16 运行时 GitHub 已经不再执行),但我们是
|
||||
# Gitea:Gitea 的 runner 用自己的 node 跑 JS action,v3 能正常把产物传上去;反过来
|
||||
# v4 在 Gitea 上**根本跑不了** —— v4 里的 @actions/artifact 会拒绝非 github.com
|
||||
# 的服务器(GHESNotSupportedError,原文见 .github/workflows/build-client.yml 文件头
|
||||
# 第 3 条,那是 2026-10-15 第一次真实运行的日志里抄下来的)。
|
||||
paths:
|
||||
.github/workflows/*.yml:
|
||||
ignore:
|
||||
- 'the runner of "actions/upload-artifact@v3" action is too old to run on GitHub Actions'
|
||||
+217
-112
@@ -1,42 +1,95 @@
|
||||
# 构建桌面客户端(desktop/,Tauri v2 + React)。
|
||||
#
|
||||
# 产物都挂在这次运行下面(Actions 页面直接下载),保留 14 天:
|
||||
# Linux x64 deb + AppImage cam-desktop_<版本>_amd64.deb / .AppImage ← 默认每次编
|
||||
# Windows x64 NSIS 安装包 cam-desktop_<版本>_x64-setup.exe ← 有 runner 才编
|
||||
# macOS 通用 .app + .dmg arm64 与 x86_64 合成一个包 ← 有 runner 才编
|
||||
# Linux x64 deb + AppImage RMIC-2026_<版本>_amd64.deb / .AppImage ← 默认每次编
|
||||
# Windows x64 NSIS 安装包 RMIC-2026_<版本>_x64-setup.exe ← 默认每次编
|
||||
# macOS 通用 .app + .dmg arm64 与 x86_64 合成一个包 ← 还没有机器,手动勾选才编
|
||||
#
|
||||
# ── 触发 ─────────────────────────────────────────────────────────────────────
|
||||
# * push 到 main/master —— 只认 desktop/、protocol/ 与本文件的改动,改固件不会
|
||||
# 白占 runner
|
||||
# * 打 v* tag、开 PR
|
||||
# * 手动 Run workflow:逐个平台勾选,不进 paths 过滤("就想编一次"走它)
|
||||
# Windows 与 macOS 两个 job 目前默认不跑,原因与打开方式见下面第 1 条。
|
||||
# 平台开关一律看勾选框(理由见下面第 4 条)。
|
||||
#
|
||||
# ── 跑在 Gitea 上要先对上的两件事 ────────────────────────────────────────────
|
||||
# 1. runner 标签是注册 runner 时自己起的名字,猜不到,所以三个 job 都写成
|
||||
# "仓库变量优先、常见标签兜底":
|
||||
# runs-on: ${{ vars.RUNNER_LINUX || 'ubuntu-latest' }}
|
||||
# 标签对不上时 job 是**排队**而不是报错 —— 排队会一直占着这次运行不结束,
|
||||
# 所以第一次跑之前先看一眼 runner 页面上那几行标签。
|
||||
# 所以上线新平台前先看一眼 runner 页面上那几行标签。
|
||||
#
|
||||
# 本仓库目前只有 Linux 的两个 runner 标签(ubuntu-latest / ubuntu-24.04 /
|
||||
# ubuntu-22.04),所以**默认只跑 Linux**:Windows 与 macOS 两个 job 要等
|
||||
# 仓库变量 ENABLE_WINDOWS / ENABLE_MACOS 设成 true 才会跑(手动 Run workflow
|
||||
# 时另看勾选框),机器的标签则用 RUNNER_WINDOWS / RUNNER_MACOS 指过去。
|
||||
# 等那两台机器上的 runner 注册好再打开,否则每次 push 都会多出两个永远排队的
|
||||
# job。
|
||||
# 2026-10-15 实测在线的(括号里是它们自己声明的标签):
|
||||
# * simba-runner-01 (ubuntu-latest / ubuntu-24.04 / ubuntu-22.04)
|
||||
# * windows-runner-01 (windows-latest / windows-2025 / windows-2022)
|
||||
# * macOS:还没有
|
||||
# 所以 Windows 默认就跑;macOS 的 job 写成"手动勾选才跑"。
|
||||
#
|
||||
# 为什么不能拿 Linux runner 交叉编出来:
|
||||
# * Windows:Rust 侧用 mingw 交叉编还能出个裸 exe,但 tauri 的 NSIS 打包器
|
||||
# 本身只能在 Windows 上跑,出不了安装包;
|
||||
# * macOS:要 Apple SDK 与 codesign/lipo,只能在 macOS 上编。
|
||||
# 所以这两个平台的 job 留着是为了"机器到位那天直接开",不是摆设。
|
||||
# 那两台机器上要预装好:Windows 的 MSVC Build Tools(link.exe)、macOS 的
|
||||
# Xcode Command Line Tools,以及 rustup(dtolnay/rust-toolchain 靠它装工具链)。
|
||||
# Linux 的 job 通常跑在容器里,所以没装 rustup 也没关系 —— 见下面那步兜底。
|
||||
# Windows runner 上要预装好 MSVC Build Tools(link.exe)、WebView2 运行时与
|
||||
# rustup(dtolnay/rust-toolchain 靠它装工具链);macOS 上要 Xcode Command Line
|
||||
# Tools + rustup。Linux 的 job 通常跑在容器里,没装 rustup 也没关系 —— 见兜底那步。
|
||||
# 2. 这个文件放在 .github/workflows/ 下就够:Gitea 的 [actions] WORKFLOW_DIRS
|
||||
# 默认值就是 [".gitea/workflows", ".github/workflows"](源码里的默认值,且
|
||||
# .yml 与 .yaml 都认),所以两个目录都是原生支持的,不用挪。
|
||||
# 3. 两处"GitHub 能跑、Gitea 上不行"的地方,都是 2026-10-15 第一次真实运行的
|
||||
# 日志里钉出来的:
|
||||
# * 传产物用 upload-artifact@v3,**不能用 v4**。v4 里的 @actions/artifact 会
|
||||
# 拒绝非 github.com 的服务器,日志原文:
|
||||
# GHESNotSupportedError: @actions/artifact v2.0.0+, upload-artifact@v4+
|
||||
# and download-artifact@v4+ are not currently supported on GHES
|
||||
# Gitea 实现的是 v3 那套接口(ACTIONS_RUNTIME_URL),所以 v3 能传上去。
|
||||
# * 一个缓存都没开。这台实例的缓存服务在 job 容器里连不上,日志原文:
|
||||
# Failed to restore: getCacheEntry failed: connect ETIMEDOUT 172.18.0.2:37315
|
||||
# setup-node 的 cache 每次要干等 4 分 39 秒才放弃(两个 job 各一次)。
|
||||
# 等缓存能从容器访问到了再加回来,写法就是:
|
||||
# setup-node 的 with 里加:cache: pnpm / cache-dependency-path: desktop/pnpm-lock.yaml
|
||||
# 各 job 里加:
|
||||
# - uses: actions/cache@v4
|
||||
# with:
|
||||
# path: |
|
||||
# ~/.cargo/registry/index
|
||||
# ~/.cargo/registry/cache
|
||||
# ~/.cargo/git/db
|
||||
# key: ${{ runner.os }}-cargo-registry-${{ hashFiles('protocol/Cargo.lock', 'desktop/core/Cargo.lock', 'desktop/src-tauri/Cargo.lock') }}
|
||||
# restore-keys: ${{ runner.os }}-cargo-registry-
|
||||
# 4. 平台开关只用 github.event.inputs,**不用仓库变量**。2026-10-15 那次 Windows# runner 明明在线、job 也确实派给了它(它自己的日志里写着
|
||||
# "Runner labels: windows-latest, windows-2025, windows-2022"),产物却一直是
|
||||
# skipped —— 卡在 job 级 if 的 `vars.ENABLE_WINDOWS == 'true'` 上:Gitea 的
|
||||
# runner 在 job 级 if 里拿不到(或拿不对)vars 上下文,而这个条件写反的代价是
|
||||
# "静悄悄不编",最难发现。同一份文件里 `github.event.inputs.linux != 'false'`
|
||||
# 这个形式在 runner 日志里能看到正常求值(evaluating expression '...' evaluated
|
||||
# to 'true'),所以平台开关统一成这种:
|
||||
# * Linux / Windows:`github.event.inputs.<平台> != 'false'` → 默认跑,
|
||||
# 手动触发时取消勾选就不跑
|
||||
# * macOS:`github.event.inputs.macos == 'true'` → 默认不跑,等有机器了改成
|
||||
# 上一种形式即可
|
||||
# runs-on 里的 vars(RUNNER_*)不受影响,标签不匹配时用那个覆盖。
|
||||
# 5. Windows runner 的 job 跑在**低完整性**上(实测 Mandatory Label\Low Mandatory
|
||||
# Level, S-1-16-4096),而 Windows 不允许低完整性往中完整性写:用户目录
|
||||
# (~/.rustup、~/.cargo、%TEMP%、%LOCALAPPDATA%)统统 Access denied (os error 5)
|
||||
# —— ACL 是干净的(SYSTEM / Administrators / 本人 全 F,没有任何 Deny),拦它的是
|
||||
# 完整性级别,所以"放宽权限"一点用都没有。表现是一串看着毫不相干的错误:
|
||||
# * Git Bash 的 mktemp 写 /tmp 被拒(它的 /tmp 跟着 TEMP 走;TEMP 不可写时退回
|
||||
# <Git 安装目录>\tmp,那个目录普通权限进不去);
|
||||
# * rustup: could not create temp file C:\Users\admin\.rustup\tmp\xxx_file
|
||||
# 处置:Windows job 把能重定向的都指到 runner 自己的工作目录
|
||||
# (C:\Users\admin\runner\windows\cache,job 内写探测实测可写)——
|
||||
# RUSTUP_HOME / CARGO_HOME / npm_config_cache / npm_config_store_dir /
|
||||
# RUNNER_TOOL_CACHE / LOCALAPPDATA / APPDATA,加上 TEMP/TMP/TMPDIR、
|
||||
# pnpm/action-setup 的 dest,以及 **USERPROFILE**。
|
||||
# 最后这个是被 pnpm 逼出来的:它的 store 操作锁不在 store-dir 下面,也不看
|
||||
# LOCALAPPDATA,而是按 <USERPROFILE>\AppData\Local\pnpm-store-operation-locks
|
||||
# 算 —— 只把 LOCALAPPDATA 指走它照样去开原来的路径(报错原文见 run 8 的日志),
|
||||
# 把 USERPROFILE 指走它才跟着走(本机实测:探针 profile 下立刻出现
|
||||
# all-stores.lock)。
|
||||
# 想从根上解决:让 gitea-runner 以**中完整性**启动(在普通终端里跑
|
||||
# run-runner.ps1,而不是从被沙箱化的低完整性 shell 里拉起来),那样这些重定向
|
||||
# 就都可以撤掉。这台机器上 runner 由计划任务 GiteaRunner-Windows(admin /
|
||||
# Limited)拉起,但进程链上的完整性已经落到了 Low。
|
||||
#
|
||||
# ── 三个平台都显式写 --target,这不是啰嗦 ────────────────────────────────────
|
||||
# desktop/.cargo/config.toml 里钉着 `target = "aarch64-apple-darwin"`(本机开发用;
|
||||
@@ -80,18 +133,18 @@ on:
|
||||
- ".github/workflows/build-client.yml"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
# Linux 的 runner 有,默认勾上;另两个平台目前没有 runner,默认不勾 ——
|
||||
# 勾了而没有对应 runner,那个 job 会一直排队。
|
||||
# Linux 与 Windows 的 runner 都在线,默认勾上;macOS 还没有机器,默认不勾
|
||||
# (勾了会一直排队)。开关为什么不用仓库变量见文件头第 4 条。
|
||||
linux:
|
||||
description: "构建 Linux(deb + AppImage)"
|
||||
type: boolean
|
||||
default: true
|
||||
windows:
|
||||
description: "构建 Windows(NSIS 安装包)——需要 Windows runner"
|
||||
description: "构建 Windows(NSIS 安装包)"
|
||||
type: boolean
|
||||
default: false
|
||||
default: true
|
||||
macos:
|
||||
description: "构建 macOS(通用 dmg)——需要 macOS runner"
|
||||
description: "构建 macOS(通用 dmg)——还没有 macOS runner,勾了会排队"
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
@@ -121,7 +174,11 @@ jobs:
|
||||
runs-on: ${{ vars.RUNNER_LINUX || 'ubuntu-latest' }}
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
# 版本号从标签算(git describe / rev-list --count)。默认的浅克隆没有标签、
|
||||
# 提交数恒为 1,version.mjs --check 会误报「版本已过期」——必须先取全历史。
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# runner 镜像里多半没有 rustup,而 dtolnay/rust-toolchain 靠它装工具链。
|
||||
# 这里只装一个最小的 rustup 本身,工具链交给下面那个 action —— 这样自建
|
||||
@@ -148,7 +205,8 @@ jobs:
|
||||
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||
fi
|
||||
|
||||
# pnpm 必须在 setup-node 之前:setup-node 的 cache: pnpm 要用它算 store 路径。
|
||||
# pnpm 装在 setup-node 之前:这一步只靠 runner 镜像里自带的 node(实测
|
||||
# /opt/hostedtoolcache 里有 24.x),而下面的 pnpm install 要用它。
|
||||
- name: 装 pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
@@ -158,13 +216,17 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: pnpm
|
||||
cache-dependency-path: desktop/pnpm-lock.yaml
|
||||
|
||||
- name: 前端依赖(严格按 lock 装)
|
||||
working-directory: desktop
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
# 版本号与改动日志是从 git 历史算出来的(scripts/version.mjs)。这一步确认
|
||||
# 仓库里的 version.json 没有被落下:有新提交却没重新生成时,装包里的版本会
|
||||
# 悄悄停在旧值上,而那种错误要到发布之后才会被发现。
|
||||
- name: 版本一致性(version.json 是否跟上了 git 历史)
|
||||
run: node scripts/version.mjs --check
|
||||
|
||||
- name: 类型检查(tsc --noEmit)
|
||||
working-directory: desktop
|
||||
run: pnpm run check
|
||||
@@ -178,21 +240,6 @@ jobs:
|
||||
with:
|
||||
targets: x86_64-unknown-linux-gnu
|
||||
|
||||
# 只缓存 registry(crate 源码),不缓存 target/:自建 runner 的容器每次都是
|
||||
# 干净的,几 GB 的 target 缓存拉下来的时间常常比重新编译还长,磁盘也扛不住。
|
||||
# 缓存服务在不同 Gitea 版本上差异较大,所以失败不让它拖垮构建。
|
||||
- name: cargo 缓存
|
||||
uses: actions/cache@v4
|
||||
continue-on-error: true
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git/db
|
||||
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('protocol/Cargo.lock', 'desktop/core/Cargo.lock', 'desktop/src-tauri/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-registry-
|
||||
|
||||
# protocol 没有自己的 rust-toolchain.toml,会被根目录那个 `esp` 接住,
|
||||
# 所以这里必须 +stable 显式指定。
|
||||
- name: protocol 单元测试(15)
|
||||
@@ -217,7 +264,11 @@ jobs:
|
||||
TARGET: x86_64-unknown-linux-gnu
|
||||
CARGO_BUILD_TARGET: x86_64-unknown-linux-gnu
|
||||
steps:
|
||||
# 版本号从标签算(git describe / rev-list --count)。默认的浅克隆没有标签、
|
||||
# 提交数恒为 1,version.mjs --check 会误报「版本已过期」——必须先取全历史。
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 确保有 rustup
|
||||
run: |
|
||||
@@ -272,25 +323,11 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: pnpm
|
||||
cache-dependency-path: desktop/pnpm-lock.yaml
|
||||
|
||||
- name: 前端依赖(严格按 lock 装)
|
||||
working-directory: desktop
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: cargo 缓存
|
||||
uses: actions/cache@v4
|
||||
continue-on-error: true
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git/db
|
||||
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('protocol/Cargo.lock', 'desktop/core/Cargo.lock', 'desktop/src-tauri/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-registry-
|
||||
|
||||
# 上面 apt 装了 ffmpeg,所以 recording.rs 里那几条"真的起 ffmpeg 并读回帧数"
|
||||
# 的测试不会走 has_ffmpeg() 的跳过分支,是真的在验。
|
||||
# 如果这台 runner 上它们长期不稳定,删掉这一步即可 —— 它们不在守门 job 里,
|
||||
@@ -305,9 +342,9 @@ jobs:
|
||||
run: pnpm tauri build --target ${{ env.TARGET }} --bundles deb,appimage
|
||||
|
||||
- name: 上传产物
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: cam-desktop-linux-x64
|
||||
name: rmic-2026-linux-x64
|
||||
# 一个都没找到就报错:产物路径写错却"构建成功"是最难发现的一种失败。
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
@@ -321,67 +358,149 @@ jobs:
|
||||
windows:
|
||||
name: Windows x64(NSIS 安装包)
|
||||
needs: checks
|
||||
# 默认不跑:本仓库目前没有 Windows runner,跑了只会一直排队。有了机器之后
|
||||
# 在仓库变量里加 ENABLE_WINDOWS=true 就会跟着每次 push 自动跑;手动触发时
|
||||
# 勾 "构建 Windows" 也能单独跑一次。标签用 RUNNER_WINDOWS 指。
|
||||
if: >-
|
||||
${{
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.windows == 'true')
|
||||
|| (github.event_name != 'workflow_dispatch' && vars.ENABLE_WINDOWS == 'true')
|
||||
}}
|
||||
# 自建 Windows runner 上要有 MSVC Build Tools(tauri 的 Rust 侧要 link.exe)、
|
||||
# WebView2 运行时(装了包的应用要它才能起窗口;打包本身不需要)与 rustup。
|
||||
# 默认就跑:windows-runner-01 在线(自报标签 windows-latest)。
|
||||
# 开关用 inputs 而不是仓库变量的原因见文件头第 4 条 —— 这个坑正是它踩出来的。
|
||||
if: ${{ github.event.inputs.windows != 'false' }}
|
||||
# 这台机器上要预装:MSVC Build Tools(tauri 的 Rust 侧要 link.exe)、WebView2
|
||||
# 运行时(装了包的应用要它才能起窗口;打包本身不需要)与 rustup。
|
||||
runs-on: ${{ vars.RUNNER_WINDOWS || 'windows-latest' }}
|
||||
timeout-minutes: 120
|
||||
env:
|
||||
TARGET: x86_64-pc-windows-msvc
|
||||
CARGO_BUILD_TARGET: x86_64-pc-windows-msvc
|
||||
# 见文件头第 5 条:这台 runner 的 job 跑在**低完整性**(Low Mandatory Level,
|
||||
# S-1-16-4096)上,而 Windows 不允许低完整性往中完整性写 —— 用户目录全中招:
|
||||
# ~/.rustup、~/.cargo、%TEMP%、%LOCALAPPDATA% 下新建任何东西都是
|
||||
# "Access denied (os error 5)",ACL 再宽松也没用(没有 Deny ACE,是完整性
|
||||
# 级别拦的)。所以 CI 用到的每个"家目录"都挪到 runner 自己的工作目录下,
|
||||
# 那几处在 job 内的写探测里实测可写。换一台 Windows runner 时改这几行。
|
||||
RUSTUP_HOME: C:\Users\admin\runner\windows\cache\ci-rustup
|
||||
CARGO_HOME: C:\Users\admin\runner\windows\cache\ci-cargo
|
||||
# LOCALAPPDATA/APPDATA 也得改:pnpm 12 的 store 锁与 cache/state 都挂在
|
||||
# %LOCALAPPDATA% 下(报错原文:pnpm-store-operation-locks\all-stores.lock:
|
||||
# 拒绝访问),tauri 打包时下载的 NSIS 也放这儿。本机实测:把 LOCALAPPDATA
|
||||
# 指到可写目录后,pnpm 会在那里建 pnpm-cache / pnpm-state,install 正常。
|
||||
LOCALAPPDATA: C:\Users\admin\runner\windows\cache\ci-localappdata
|
||||
APPDATA: C:\Users\admin\runner\windows\cache\ci-appdata
|
||||
npm_config_cache: C:\Users\admin\runner\windows\cache\ci-npm
|
||||
RUNNER_TOOL_CACHE: C:\Users\admin\runner\windows\cache\ci-toolcache
|
||||
steps:
|
||||
# 版本号从标签算(git describe / rev-list --count)。默认的浅克隆没有标签、
|
||||
# 提交数恒为 1,version.mjs --check 会误报「版本已过期」——必须先取全历史。
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# 宿主模式的 Windows runner 上,PATH 里的 `bash` 是 C:\Windows\System32\bash.exe
|
||||
# —— WSL 的启动器,而 WSL 在 runner 账户下起不来,报 E_ACCESSDENIED(日志里是
|
||||
# 一串 GBK 被当 UTF-8 解出来的乱码 + https://github.com/microsoft/WSL/issues)。
|
||||
# 于是任何 `shell: bash` 的步骤、以及用 bash 写的 composite action
|
||||
# (dtolnay/rust-toolchain 就是),都会在 0 秒内失败。
|
||||
# Git for Windows 自带 bash,把它的 bin 前置到 PATH —— GITHUB_PATH 的语义就是
|
||||
# **前置**插入,所以它会盖过 System32 那个 —— 后面这些步骤才跑得起来。
|
||||
#
|
||||
# 换掉 bash 之后还有第二个坑:这个 job 的环境里 TEMP/TMP 是空的,而 Git Bash 的
|
||||
# /tmp 是从 TEMP 推出来的;没有 TEMP 它会退回 <Git 安装目录>\tmp
|
||||
# (C:\Program Files\Git\tmp),那个目录普通权限写不进去,于是 dtolnay 里那句
|
||||
# log=$(mktemp) 直接挂掉:
|
||||
# mktemp: failed to create file via template '/tmp/tmp.XXXXXXXXXX': Permission denied
|
||||
# 所以顺手给 job 指定一个自己建的可写目录(workspace 的同级 ci-tmp)。
|
||||
# 两个坑都是 2026-10-15 真跑 Windows job 时踩出来的,本地按这样改完后
|
||||
# mktemp、rustup toolchain install、rustup target add 全部正常。
|
||||
- name: 让 bash 指向 Git for Windows,并给 job 一个可写的临时目录
|
||||
shell: pwsh
|
||||
run: |
|
||||
if (-not $env:GITHUB_PATH) { throw '这个 runner 没提供 GITHUB_PATH,没法给后续步骤改 PATH' }
|
||||
if (-not $env:GITHUB_ENV) { throw '这个 runner 没提供 GITHUB_ENV,没法给后续步骤加变量' }
|
||||
if (-not $env:GITHUB_WORKSPACE) { throw '这个 runner 没提供 GITHUB_WORKSPACE' }
|
||||
|
||||
$gitBash = 'C:\Program Files\Git\bin'
|
||||
if (-not (Test-Path (Join-Path $gitBash 'bash.exe'))) {
|
||||
throw "找不到 $gitBash\bash.exe:这台机器上没装 Git for Windows,bash 会落到 WSL 上"
|
||||
}
|
||||
Add-Content -Path $env:GITHUB_PATH -Value $gitBash -Encoding ascii
|
||||
|
||||
$tmpRoot = Join-Path (Split-Path -Parent $env:GITHUB_WORKSPACE) 'ci-tmp'
|
||||
New-Item -ItemType Directory -Force -Path $tmpRoot | Out-Null
|
||||
Add-Content -Path $env:GITHUB_ENV -Value "TEMP=$tmpRoot" -Encoding ascii
|
||||
Add-Content -Path $env:GITHUB_ENV -Value "TMP=$tmpRoot" -Encoding ascii
|
||||
Add-Content -Path $env:GITHUB_ENV -Value "TMPDIR=$($tmpRoot -replace '\\', '/')" -Encoding ascii
|
||||
|
||||
# 第三个坑:pnpm 的 store 操作锁是按 <USERPROFILE>\AppData\Local\
|
||||
# pnpm-store-operation-locks 算的 —— 实测只把 LOCALAPPDATA 指走它不跟,
|
||||
# 只把 USERPROFILE 指走它就跟着走(换完 USERPROFILE 后探针目录里立刻出现
|
||||
# all-stores.lock)。所以 USERPROFILE 也得指到能写的地方,并且先把
|
||||
# AppData\Local、AppData\Roaming 建好。
|
||||
$profileRoot = Join-Path (Split-Path -Parent $env:GITHUB_WORKSPACE) 'ci-profile'
|
||||
New-Item -ItemType Directory -Force -Path (Join-Path $profileRoot 'AppData\Local'), (Join-Path $profileRoot 'AppData\Roaming') | Out-Null
|
||||
Add-Content -Path $env:GITHUB_ENV -Value "USERPROFILE=$profileRoot" -Encoding ascii
|
||||
|
||||
"bash = $(Join-Path $gitBash 'bash.exe')"
|
||||
"TEMP/TMP/TMPDIR = $tmpRoot"
|
||||
"USERPROFILE = $profileRoot"
|
||||
|
||||
- name: 装 Rust(stable)
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: x86_64-pc-windows-msvc
|
||||
|
||||
- name: 装 pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: 装 Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: pnpm
|
||||
cache-dependency-path: desktop/pnpm-lock.yaml
|
||||
|
||||
# 这里**不用** pnpm/action-setup:它装的是 standalone 的 pnpm 二进制,而那个
|
||||
# 二进制在这台 runner 的低完整性沙箱里走到"把包装进 virtual store"时必崩:
|
||||
# thread '<unknown>' (6716) has overflowed its stack
|
||||
# corepack 拉下来的是同一个二进制(也崩),所以改成用 npm 装 JS 版 pnpm ——
|
||||
# 它跑在 node 上,是另一条代码路径。装到 node 自己的目录里(RUNNER_TOOL_CACHE
|
||||
# 已经指到可写位置,见 job env)。
|
||||
- name: 装 pnpm(npm 装 JS 版,绕开 standalone 二进制)
|
||||
shell: pwsh
|
||||
run: |
|
||||
# 前面几次尝试在 node 目录里留下过 pnpm/pnpx 的影子文件,npm 会以
|
||||
# EEXIST 拒绝覆盖,先清掉(node 目录在 RUNNER_TOOL_CACHE 下,可写)。
|
||||
$nodeDir = Split-Path (Get-Command node).Source
|
||||
foreach ($n in 'pnpm', 'pnpx', 'pnpm.cmd', 'pnpx.cmd', 'pnpm.ps1', 'pnpx.ps1') {
|
||||
Remove-Item (Join-Path $nodeDir $n) -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
npm install -g pnpm@12.8.1
|
||||
if ($LASTEXITCODE -ne 0) { throw "npm 装 pnpm 失败(exit $LASTEXITCODE)" }
|
||||
"pnpm = $((Get-Command pnpm).Source)"
|
||||
pnpm --version
|
||||
|
||||
- name: 前端依赖(严格按 lock 装)
|
||||
working-directory: desktop
|
||||
run: pnpm install --frozen-lockfile
|
||||
shell: pwsh
|
||||
# 这台 runner 的 job 跑在低完整性沙箱里(见文件头第 5 条),standalone 的
|
||||
# pnpm 到"把包装进 virtual store"那一步会直接栈溢出:
|
||||
# thread '<unknown>' (6716) has overflowed its stack
|
||||
# (store 已经在可写目录、也改成 copy 导入了,仍然复现;同样的命令在普通
|
||||
# Medium 会话里跑得好好的。)所以先试 standalone,崩了就退回 corepack:
|
||||
# 让 node 去跑 pnpm 的 JS 实现,绕开那个二进制。
|
||||
run: |
|
||||
$pnpmArgs = @('install', '--frozen-lockfile',
|
||||
'--store-dir', 'C:\Users\admin\runner\windows\cache\ps',
|
||||
'--package-import-method=copy', '--reporter=append-only')
|
||||
pnpm @pnpmArgs
|
||||
if ($LASTEXITCODE -eq 0) { exit 0 }
|
||||
"::warning::standalone pnpm 失败(exit $LASTEXITCODE),改用 corepack + node 跑 pnpm"
|
||||
corepack enable
|
||||
corepack prepare pnpm@12.8.1 --activate
|
||||
pnpm @pnpmArgs
|
||||
exit $LASTEXITCODE
|
||||
|
||||
- name: cargo 缓存
|
||||
uses: actions/cache@v4
|
||||
continue-on-error: true
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git/db
|
||||
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('protocol/Cargo.lock', 'desktop/core/Cargo.lock', 'desktop/src-tauri/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-registry-
|
||||
|
||||
# nsis 是纯安装包,不需要 WiX;tauri 会自己把 NSIS 下到用户缓存里,所以
|
||||
# 这台 runner 要能访问 GitHub(离线环境改成 --bundles msi 也一样要下 WiX)。
|
||||
# nsis 是纯安装包,不需要 WiX;tauri 会自己把 NSIS 下到用户缓存里(这个缓存
|
||||
# 也跟着 LOCALAPPDATA 走,见上面 job env),所以这台 runner 要能访问 GitHub
|
||||
# (离线环境改成 --bundles msi 也一样要下 WiX)。
|
||||
- name: 打包(tauri build)
|
||||
working-directory: desktop
|
||||
run: pnpm tauri build --target ${{ env.TARGET }} --bundles nsis
|
||||
|
||||
- name: 上传产物
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: cam-desktop-windows-x64
|
||||
name: rmic-2026-windows-x64
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
path: |
|
||||
@@ -393,14 +512,10 @@ jobs:
|
||||
macos:
|
||||
name: macOS 通用(.app + .dmg)
|
||||
needs: checks
|
||||
# 同 Windows:默认不跑,有机器之后设仓库变量 ENABLE_MACOS=true,标签用
|
||||
# RUNNER_MACOS 指。
|
||||
if: >-
|
||||
${{
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.macos == 'true')
|
||||
|| (github.event_name != 'workflow_dispatch' && vars.ENABLE_MACOS == 'true')
|
||||
}}
|
||||
# 自建 macOS runner 上要有 Xcode Command Line Tools(链接要它)与 rustup。
|
||||
# 还没有 macOS runner,所以只有"手动触发 + 勾上"才跑;等机器注册好,把这一行
|
||||
# 换成 `github.event.inputs.macos != 'false'` 就变成默认跑(同 Windows)。
|
||||
if: ${{ github.event.inputs.macos == 'true' }}
|
||||
# 那台机器上要有 Xcode Command Line Tools(链接要它)与 rustup。
|
||||
runs-on: ${{ vars.RUNNER_MACOS || 'macos-latest' }}
|
||||
timeout-minutes: 150
|
||||
env:
|
||||
@@ -409,7 +524,11 @@ jobs:
|
||||
# cargo 步骤,所有目标都由 tauri build 的 --target 交代。
|
||||
TARGET: universal-apple-darwin
|
||||
steps:
|
||||
# 版本号从标签算(git describe / rev-list --count)。默认的浅克隆没有标签、
|
||||
# 提交数恒为 1,version.mjs --check 会误报「版本已过期」——必须先取全历史。
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# 通用包要两个架构的 std 都在。
|
||||
- name: 装 Rust(stable + 两个 mac 目标)
|
||||
@@ -426,25 +545,11 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: pnpm
|
||||
cache-dependency-path: desktop/pnpm-lock.yaml
|
||||
|
||||
- name: 前端依赖(严格按 lock 装)
|
||||
working-directory: desktop
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: cargo 缓存
|
||||
uses: actions/cache@v4
|
||||
continue-on-error: true
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git/db
|
||||
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('protocol/Cargo.lock', 'desktop/core/Cargo.lock', 'desktop/src-tauri/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-registry-
|
||||
|
||||
# 只想要当前架构、想省一半编译时间的话,把 --target 换成 aarch64-apple-darwin
|
||||
# (Apple Silicon 的 runner)并去掉上面 targets 里的 x86_64 即可。
|
||||
- name: 打包(tauri build)
|
||||
@@ -454,9 +559,9 @@ jobs:
|
||||
# 第二行是兜底:不同 tauri-cli 版本给通用包放的目录略有差异,宁可多写一行,
|
||||
# 也不要上传时静默少东西。(注意 path 是块标量,里面写 # 是路径不是注释。)
|
||||
- name: 上传产物
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: cam-desktop-macos-universal
|
||||
name: rmic-2026-macos-universal
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
path: |
|
||||
|
||||
+12
-2
@@ -12,6 +12,13 @@
|
||||
/target/
|
||||
/protocol/target/
|
||||
/armor/target/
|
||||
/.embuild/
|
||||
/managed_components/
|
||||
/sdkconfig
|
||||
/sdkconfig.old
|
||||
/sdkconfig.*
|
||||
!/sdkconfig.defaults
|
||||
!/sdkconfig.defaults.*
|
||||
|
||||
# 本机专属的 cargo 注册表缓存。
|
||||
#
|
||||
@@ -29,6 +36,9 @@
|
||||
# macOS 的目录元数据。不锚定,子目录里的也一起忽略。
|
||||
.DS_Store
|
||||
|
||||
# Python diagnostic helpers.
|
||||
__pycache__/
|
||||
|
||||
# `docs/simba_docs`:工作区里另起的一个 Astro 文档站,与本仓库的目录结构无关,
|
||||
# 也不属于本项目的产物。留着不跟踪,免得以后 `git add -A` 顺手把它带进来。
|
||||
/docs/
|
||||
# 也不属于本项目的产物。只忽略它本身;docs/ 下的项目文档要跟踪。
|
||||
/docs/simba_docs/
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
# 仓库协作约定
|
||||
|
||||
本文件适用于整个仓库,供开发者和代码助手使用。先阅读 [README.md](README.md) 与 [文档首页](docs/index.md),再按任务进入对应子项目。子目录若新增更具体的 `AGENTS.md`,按其适用范围执行。
|
||||
|
||||
## 项目边界
|
||||
|
||||
RMIC::2026 包含 ESP32-S3 图传固件、ESP32-C3 装甲板固件和 Tauri 桌面端。它们共享部分协议代码,但不共享同一个构建目标。
|
||||
|
||||
| 路径 | 修改内容 | 需要注意 |
|
||||
| --- | --- | --- |
|
||||
| `src/`、根 `build.rs` | S3 采集、发送、重连及构建参数 | Rust `std` + ESP-IDF,默认目标为 Xtensa |
|
||||
| `components/cam_wrapper/` | 相机 C/Rust FFI | 同步核对 C 结构体、绑定与缓冲所有权 |
|
||||
| `components/esp32-camera/` | 本地驱动及补丁 | 保留上游结构和本地补丁说明 |
|
||||
| `protocol/` | 线路编解码与帧重组 | 固件和桌面共用,保持协议兼容性 |
|
||||
| `desktop/core/` | 网络、配置、密码、装甲板与槽位 | 保持独立于 UI 与 Tauri,可单独测试 |
|
||||
| `desktop/src-tauri/` | 本地通道、命令、录制与维护 | 涉及文件、子进程、串口及 WebView 边界 |
|
||||
| `desktop/src/` | React、TypeScript、canvas 与样式 | 复用已有组件、绘制函数和样式令牌 |
|
||||
| `armor/` | C3 固件与受击规则 | 独立 workspace,目标为 RISC-V |
|
||||
| `scripts/` | 构建、打包、版本和诊断 | 修改入口或输出路径时同步文档与调用方 |
|
||||
| `reference/armor/` | 早期 C 版参考工程 | 默认用于核对,不作为当前实现入口 |
|
||||
|
||||
## 工具链与执行目录
|
||||
|
||||
- 根 `rust-toolchain.toml` 固定为 `esp-1.97.0.0`,根 `.cargo/config.toml` 默认目标是 `xtensa-esp32s3-espidf`,并启用 `build-std`。
|
||||
- `desktop/` 使用 Rust stable,但默认 Cargo 目标仍为 `aarch64-apple-darwin`;`protocol/` 和 `armor/armor-protocol/` 也有宿主目标覆盖。换平台时显式传入实际目标。
|
||||
- Windows x64 的宿主目标是 `x86_64-pc-windows-msvc`。主机检查显式使用 `+stable`、`--target` 和 `--config 'unstable.build-std=[]'`,避免继承固件配置。
|
||||
- S3 默认 ESP-IDF 为 v5.5.5;装甲板原环境使用 v6.0.1。`armor/.cargo/config.toml` 含原开发机的 `IDF_PATH`,构建前核对,不假设该路径在其他机器存在。
|
||||
- Cargo 根据执行目录查找 `.cargo/config.toml`;仅传 `--manifest-path` 不会自动切换到对应子项目的配置。需要依赖子项目配置时,先进入该目录。
|
||||
- 固件通过 `scripts/build.ps1` 或 `scripts/build.sh` 构建,以加载 `.env` 并设置组件、分区表路径。直接执行 `cargo build` 不会自动加载 `.env`。
|
||||
- 桌面前端使用 pnpm 和 `desktop/pnpm-lock.yaml`,安装时优先 `pnpm install --frozen-lockfile`。不要同时维护 npm / Yarn 锁文件。
|
||||
|
||||
环境准备和平台细节见[固件入门](docs/setup/firmware.md)、[客户端说明](docs/desktop/client.md)和[装甲板说明](docs/hardware/armor.md)。
|
||||
|
||||
## 修改方式
|
||||
|
||||
1. 开始前检查工作区差异,保留用户已有的修改;先定位行为所在模块,再修改对应层。
|
||||
2. 沿用现有命名、格式和模块划分。代码助手只处理任务相关变更,避免顺带重排全仓库、升级依赖或改动其他平台配置。
|
||||
3. 修改行为时补充能覆盖该行为的验证;纯文档或文案调整检查链接、命令、类型和显示效果即可。
|
||||
4. 完成后检查差异与空白问题,说明改了什么、做了哪些验证,以及仍未验证的范围。
|
||||
|
||||
### 协议与实时图传
|
||||
|
||||
- 线路定义以 `protocol/` 为准。修改字段、头长、消息类型或版本时,同时核对固件、桌面、`scripts/udp_probe.py` 等独立探测器和[协议文档](docs/protocol/udp.md)。
|
||||
- 视频帧必须按发送方和帧号隔离,重复分片不重复计数,不完整 JPEG 不进入解码和绘制。
|
||||
- 保留最新帧优先、有界等待和过期清理。不要让接收线程等待视频解码、写盘或 UI 消费,也不要以无限队列掩盖拥塞。
|
||||
- 修改 C 相机缓冲交接时,检查获取、归还、超时和提前返回路径,避免重复归还或泄漏。
|
||||
- 用户端绑定过滤、发现列表和裁判槽位有不同职责,不要为了显示候选设备而放开用户端视频接收限制。
|
||||
|
||||
### 桌面端与文案
|
||||
|
||||
- 业务规则放在 Rust 核心,前端使用既有快照与 IPC 接口;新增设置需核对配置序列化、默认值和旧配置兼容性。
|
||||
- 画面、准星、网格和命中反馈复用现有变换与绘制函数。改文案或布局后,按影响范围检查用户端、裁判端、设置和首次启动页面。
|
||||
- 中文表达清楚、具体,写明状态、条件和下一步。保留术语、错误原因、数字与验证边界,避免比喻式按钮和含糊的成功提示。
|
||||
- 密码限制用于防止现场误改;包内哈希校验用于检查完整性。文案不能将它们描述为不可绕过的访问控制或固件来源认证。
|
||||
- 预览和演示数据始终标明为合成数据,不用演示结果替代实际网络或硬件验证。
|
||||
|
||||
### 硬件、配置与源码保护
|
||||
|
||||
- 不提交 Wi-Fi 凭据、密码哈希、个人 SDK 绝对路径或本机缓存;不要在日志和交付说明中输出 `.env` 内容。保留已有 `.env`,示例使用占位值。
|
||||
- 仅做软件、文档或文案任务时,不执行烧录、擦除、写分区表或修改设备安全配置。涉及硬件任务时,按用户授权核对芯片、端口、分区与备份。
|
||||
- `components/esp32-camera/target/` 是驱动源码目录,不能按构建缓存删除或忽略。清理缓存时使用明确的构建目录,相关方法见[开发与验证](docs/development/firmware.md#清理构建缓存)。
|
||||
- 第三方组件的 README、许可证和本地补丁记录保留原有位置。不要把参考工程的旧实现误写成当前功能。
|
||||
|
||||
## 按变更选择验证
|
||||
|
||||
以下命令默认从仓库根目录执行,标明其他目录的除外。示例宿主为 Windows x64;其他平台用 `rustc +stable -vV` 核对 host 后替换 `--target`。
|
||||
|
||||
| 变更 | 至少检查 |
|
||||
| --- | --- |
|
||||
| 文档、链接、导航 | 文件与锚点有效,代码块闭合,命令路径正确;Mermaid 语法可解析 |
|
||||
| React / TypeScript / UI 文案 | `desktop/` 下 `pnpm build`,并预览受影响页面 |
|
||||
| 前端解码队列 | `desktop/` 下 `pnpm test:stream` |
|
||||
| 延迟校准标记 | `desktop/` 下 `pnpm test:latency` |
|
||||
| 线路编解码、帧重组 | `protocol/` 主机测试,必要时独立协议探测 |
|
||||
| 桌面业务规则 | `desktop/core/` 主机测试 |
|
||||
| Tauri 命令、录制、维护 | 后端编译检查和相关单元测试;录制测试需 ffmpeg / ffprobe |
|
||||
| 装甲板受击规则 | `armor/armor-protocol/` 主机测试 |
|
||||
| 固件发送、重连、缓冲交接 | 对应主机策略测试或 `scripts/test-latest-frame.py`;硬件表现另行实测 |
|
||||
| 版本生成脚本或生成路径 | 隔离样例检查输出位置与重复生成,再运行版本一致性检查 |
|
||||
|
||||
### 常用主机命令
|
||||
|
||||
```powershell
|
||||
cargo +stable test --locked --manifest-path protocol/Cargo.toml --target x86_64-pc-windows-msvc --config 'unstable.build-std=[]'
|
||||
cargo +stable test --locked --manifest-path desktop/core/Cargo.toml --target x86_64-pc-windows-msvc --config 'unstable.build-std=[]'
|
||||
cargo +stable test --locked --manifest-path armor/armor-protocol/Cargo.toml --target x86_64-pc-windows-msvc --config 'unstable.build-std=[]'
|
||||
cargo +stable check --locked --manifest-path desktop/src-tauri/Cargo.toml --target x86_64-pc-windows-msvc --config 'unstable.build-std=[]'
|
||||
```
|
||||
|
||||
前端命令在 `desktop/` 执行:
|
||||
|
||||
```sh
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm build
|
||||
pnpm test:stream
|
||||
pnpm test:latency
|
||||
```
|
||||
|
||||
浏览器预览:`pnpm dev`,打开 `http://127.0.0.1:1420/`;可用 `?preview=referee`、`?preview=first-run`、`?preview=password` 检查不同页面。预览不能验证 Tauri 原生能力。
|
||||
|
||||
测试通过后无需重复运行无关的大范围检查。依赖、工具链或设备条件不足时,明确记录实际执行结果和未验证项。**编译、主机测试、镜像生成不等于烧录、板上运行或硬件稳定性已验证。**
|
||||
|
||||
## 文档组织
|
||||
|
||||
- 根目录 `README.md` 介绍项目、展示架构并链接到详细文档;本文件保存仓库协作约定。子项目 README 保留简短入口。
|
||||
- 除 README 和 AGENTS 外,项目说明集中到 `docs/`,按 `setup/`、`development/`、`hardware/`、`desktop/`、`protocol/`、`maintenance/`、`testing/`、`releases/`、`design/` 归类。
|
||||
- 新增或迁移文档时,同步更新 `docs/index.md`、相对链接、标题锚点、脚本和代码中的引用。可复用既有主题时,优先修改原文。
|
||||
- 已被实现替代或明确放弃的方案可以删除,历史从 Git 查阅;保留仍有价值的实验条件、原始数字和限定。未实现需求写入 `docs/design/` 并标明状态。
|
||||
- 改写中文时保留事实、数字、命令、否定和确定程度,不将计划写成已完成,不将接收间隔写成端到端延迟。
|
||||
- README 使用 GitHub 风味 Markdown:表格用于比较,折叠块放可选步骤,提示块强调实际限制,Mermaid 解释结构和流程。使用稳定基础语法,不在图表中加入脚本或外部服务依赖。
|
||||
|
||||
## 版本与提交
|
||||
|
||||
提交标题采用 Conventional Commits:`type(scope): 说明`。破坏性改动升 major,`feat` 升 minor,`fix` / `perf` 升 patch;其他类型保持语义版本,构建号由 Git 提交数计算。
|
||||
|
||||
版本和日志由脚本生成,在根目录执行:
|
||||
|
||||
```sh
|
||||
node scripts/version.mjs --write
|
||||
node scripts/version.mjs --check
|
||||
```
|
||||
|
||||
不要手动修改 `version.json`、`desktop/src/lib/version.ts` 或 `docs/releases/changelog.md` / `.json`。`desktop/package.json` 和 `desktop/src-tauri/tauri.conf.json` 的版本字段也由同一脚本同步;其他字段按任务正常维护。提交或标签改变后,按发布流程重新生成;文档路径变更不得使日志重新出现在根目录。
|
||||
|
||||
完整规则见[版本维护](docs/development/versioning.md)。
|
||||
@@ -1,395 +1,292 @@
|
||||
# ESP32-S3 摄像头图传(Rust / ESP-IDF / UDP)
|
||||
<p align="center">
|
||||
<img src="desktop/src-tauri/icons/128x128.png" width="88" height="88" alt="Simba Robotics 队标">
|
||||
</p>
|
||||
|
||||
两块 ESP32-S3-WROOM-1-N16R8 图传板(DVP 接口接 OV5640),向队伍客户端与裁判终端推
|
||||
UDP 图传。
|
||||
# RMIC::2026 · 视野链路系统
|
||||
|
||||
固件是跑在 ESP-IDF 上的 Rust,用官方 esp32-camera 组件采集;桌面端是 **Tauri v2 +
|
||||
React**,用户端与裁判端两套页面(见 [`desktop/README.md`](desktop/README.md))。同一套
|
||||
系统里还有一队一块的**装甲板**(ESP32-C3,一主三从),负责四路击打检测、灯带与整车血量,
|
||||
并把血量广播给局域网里的两端。
|
||||
**黑龙江大学 602 实验室 · Simba Robotics**
|
||||
|
||||
```text
|
||||
protocol/ 线路格式 + 帧重组 三端共用,无 ESP-IDF 依赖,14 个单元测试
|
||||
. 图传固件 -> ESP32-S3 交叉编译,Xtensa
|
||||
armor/ 装甲板固件 -> ESP32-C3 独立 workspace,RISC-V;规则层 22 个单元测试
|
||||
desktop/ 桌面端 Tauri v2 + React;用户端(双方血条 + 瞄准器)/ 裁判端(可改队名)
|
||||
core/ 不含界面与 Tauri 的网络层、配置、密码(可单测)
|
||||
src-tauri/ Tauri 后端:推流、命令、录制
|
||||
src/ React 前端:两套页面 + HUD
|
||||
面向机器人对抗赛的局域网图传与装甲板系统。ESP32-S3 采集 OV5640 的硬件 JPEG,经 UDP 发送到桌面端;ESP32-C3 上报装甲板血量,桌面端提供操作手视野、裁判双路监看、截图录制和 USB 固件维护。
|
||||
|
||||
[](docs/setup/firmware.md)
|
||||
[](docs/hardware/armor.md)
|
||||
[](docs/desktop/client.md)
|
||||
[](docs/protocol/udp.md)
|
||||
|
||||
[文档首页](docs/index.md) · [硬件接线](docs/hardware/camera.md) · [故障排查](docs/testing/troubleshooting.md) · [改动记录](docs/releases/changelog.md) · [开发约定](AGENTS.md)
|
||||
|
||||
> [!NOTE]
|
||||
> 版本、构建号与线路协议号以 [`version.json`](version.json) 为准。改动记录从 Git 历史生成,维护方式见[版本说明](docs/development/versioning.md)。
|
||||
|
||||
## 导航
|
||||
|
||||
- [系统架构](#系统架构)
|
||||
- [功能与验证范围](#功能与验证范围)
|
||||
- [快速开始](#快速开始)
|
||||
- [发现与收流时序](#发现与收流时序)
|
||||
- [仓库结构](#仓库结构)
|
||||
- [开发与验证](#开发与验证)
|
||||
- [文档与后续工作](#文档与后续工作)
|
||||
|
||||
## 系统架构
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Camera["图传板 · ESP32-S3"]
|
||||
Sensor["OV5640<br/>硬件 JPEG"] --> Firmware["采集与分片<br/>最新帧发送"]
|
||||
end
|
||||
|
||||
subgraph Armor["装甲板 · ESP32-C3"]
|
||||
Piezo["四路压电片"] --> Rules["受击规则与整车血量"]
|
||||
Rules --> LEDs["四路 WS2812"]
|
||||
end
|
||||
|
||||
subgraph Desktop["RMIC::2026 · 桌面端"]
|
||||
Core["Rust 核心<br/>发现、重组与血量状态"]
|
||||
Backend["Tauri 后端<br/>本地通道、截图与录制"]
|
||||
UI["React / WebView<br/>用户端或裁判端"]
|
||||
Core --> Backend
|
||||
Backend -->|"本地 WebSocket:JPEG 与状态"| UI
|
||||
UI -->|"IPC:配置与操作"| Backend
|
||||
end
|
||||
|
||||
Firmware -->|"UDP 5600:Hello 与视频分片"| Core
|
||||
Core -->|"Subscribe:广播与定向续订"| Firmware
|
||||
Rules -->|"UDP 5601:血量广播"| Core
|
||||
```
|
||||
|
||||
五份产物各自独立构建,各有自己的 `.cargo/config.toml`。
|
||||
两块图传板各自发送,裁判端同时接收两路;用户端只接收绑定图传板的画面。装甲板主板连接四路传感器和灯带,其余从板不含 MCU。
|
||||
|
||||
## 拓扑
|
||||
| 网络项 | 配置 | 说明 |
|
||||
| :--- | :--- | :--- |
|
||||
| 图传板 Wi-Fi | 2.4 GHz | 板卡与客户端需处于可互通的局域网 |
|
||||
| 视频与发现 | UDP `5600` | Hello、Subscribe 与 JPEG 分片 |
|
||||
| 装甲板上报 | UDP `5601` | 每 200 ms 广播四路血量 |
|
||||
| 本地界面通道 | 回环 WebSocket + Tauri IPC | JPEG 由 WebView 解码,网络接收线程不做视频解码 |
|
||||
|
||||
```text
|
||||
┌──────────────────────────────┐
|
||||
│ 裁判终端(PC,仅显示) │
|
||||
│ 队伍一 + 队伍二 + 双方血量 │
|
||||
└───────────────┬──────────────┘
|
||||
│ 同一个局域网,广播可用
|
||||
┌────────────────────┼────────────────────┐
|
||||
│ │ │
|
||||
┌───────┴───────┐ ┌───────┴───────┐ ┌───────┴───────┐
|
||||
│ 图传板 A │ │ 图传板 B │ │ 装甲板主板 │
|
||||
│ (队伍一) │ │ (队伍二) │ │ ×2(每队一块)│
|
||||
└───────────────┘ └───────────────┘ └───────────────┘
|
||||
```
|
||||
> [!IMPORTANT]
|
||||
> 网络需要允许广播和设备间互访。请关闭 AP 客户端隔离,并允许客户端通过防火墙。VPN / 虚拟网卡可能影响广播路由,排查方法见[客户端说明](docs/desktop/client.md#windows-x64-构建)。
|
||||
|
||||
**图传板不区分队伍。** 它们只知道自己是谁(MAC 派生的 `sender_id`),"哪一边"由
|
||||
客户端决定。烧同一份固件即可,配置全在客户端一侧。
|
||||
## 功能与验证范围
|
||||
|
||||
**装甲板反过来:队伍是编译期定的**(`ARMOR_TEAM=0/1`,同时决定灯色红/蓝),因为血量
|
||||
上报里必须带 `team` 字段,而且灯色现场不该靠按键切错。见 `armor/README.md`。
|
||||
| 模块 | 已有功能 | 使用条件与边界 |
|
||||
| :--- | :--- | :--- |
|
||||
| 图传固件 | VGA 彩色 JPEG、UDP 分片、多端订阅、Wi-Fi 重连、过期帧放弃 | 当前记录约 30 fps;不同网络、板卡与画质下需复测[^performance] |
|
||||
| 用户端 | 本队画面、双方装甲条、瞄准器、偏移校准、命中反馈 | 先绑定图传板与装甲板;改绑、切换模式和退出受密码限制 |
|
||||
| 裁判端 | 双路画面、队名编辑、装甲板发现、槽位分配、截图、录制与回放 | 录制需要 ffmpeg;图传槽位可手动指定 |
|
||||
| 装甲板 | 四路受击检测与灯带、整车血量、UDP 上报 | 初始 100 血,每次受击扣 3 点;队伍号在编译期选择 |
|
||||
| 固件维护 | USB 读取设备、检查固件包、选择候选槽位、备份和写后回读 | App 写入与回读已有实机记录;完整分区切换尚未验证 |
|
||||
|
||||
裁判终端同时收两块图传板,也收两块装甲板主板在 5601 上广播的血量。队伍客户端也收
|
||||
全部板子的视频,然后在**界面层**过滤到绑定的那一块;**血量相反,两边都显示**——用户端的
|
||||
画面左上角是自己、右上角是对手。血量本来就是明文广播的(裁判端与用户端收的是同一份),而
|
||||
"自己的血与对手的血一起看"是这项运动里最基本的对照,藏起来只会让操作手看不见对手的血。
|
||||
发送端在有限预算内提交当前帧,超时后转向新帧;本地视频通道与 WebView 会放弃滞留超过 100 ms 的旧帧。这个期限约束的是本机积压,**不代表相机到屏幕的端到端延迟**。
|
||||
|
||||
## 实测性能
|
||||
> [!WARNING]
|
||||
> 固件维护会写入设备 Flash。使用前核对芯片、分区和备份,并阅读[已验证与未验证范围](docs/maintenance/firmware.md#已验证--未验证)。ESP32-S3 与 ESP32-C3 的固件不能混用。
|
||||
|
||||
VGA 640x480,传感器产出上限 22 fps(OV5640 在此配置下的实测值)。
|
||||
|
||||
| | TCP(HTTP MJPEG) | UDP(现在) |
|
||||
|---|---|---|
|
||||
| 传感器实际产出 | 22 fps | 22 fps |
|
||||
| 客户端实际收到 | **1.9 fps** | **22 fps** |
|
||||
| 实际吞吐 | 0.28 Mbit/s | 1.6 Mbit/s |
|
||||
| 丢帧 | — | 0% |
|
||||
|
||||
TCP 那一栏只用了链路能力的 1% 左右(ESP32-S3 在良好 2.4GHz 链路上 TCP 实测上限约
|
||||
20–30 Mbit/s)。**链路是空的**,帧却堵在队头阻塞后面等一个实时画面等不起的重传计时
|
||||
器。
|
||||
|
||||
UDP 没有重传与确认:缺了分片的帧整个丢弃,下一帧顶上。丢帧率实测 0%,因为带宽从来
|
||||
不是瓶颈——即便如此也只用掉 6–9%。
|
||||
|
||||
其余数字:平均帧 22 kB,约 350 包/秒,每帧约 17 个分片,客户端观测的帧间隔 7 ms。
|
||||
|
||||
## 为什么视频走 UDP 而不是 TCP
|
||||
|
||||
**延迟优先。** 丢失 5% 的画面远好于等待 500 ms 的重传。
|
||||
|
||||
**没有队头阻塞。** 一帧的任一分片卡住,后面所有帧都跟着卡——对实时画面这正好是错
|
||||
的方向。
|
||||
|
||||
**带宽不是约束。** 见上表,TCP 那栏证明了链路是空的,所以 TCP 的优势在这里换不来
|
||||
任何东西。
|
||||
|
||||
代价是丢包丢整帧。这被接受了:**撕裂的画面比过期的画面更糟**,所以接收端不做补洞,
|
||||
只等下一张完整帧。
|
||||
|
||||
## 帧率必须按计数除以墙钟
|
||||
|
||||
"22 fps" 这个数字不是对帧间隔取平均得来的,是每个窗口的**帧数增量除以窗口时长**。
|
||||
|
||||
一次 UI 轮询(33ms)之间可能完成多帧,平均间隔量的是**显示**的刷新快慢而不是**相机**
|
||||
的出帧快慢——那会把上限 22fps 的传感器报成 36fps。**一个笃定的错误数字比没有数字更
|
||||
糟**,现场你会以为还有性能余量,然后花时间去找一个不存在的问题。
|
||||
|
||||
这个错误在本项目里出现过两次(客户端的 `View::update_fps`、探测脚本的分窗打印),两
|
||||
次都是同一个形状:分子用累计值,分母用窗口值,于是报出随时间单调上升的假数字。
|
||||
[^performance]: [最新帧处理记录](docs/testing/realtime.md)、[功率与拥塞记录](docs/testing/power.md)和[延迟测量](docs/testing/latency.md)保留了配置、观察窗口与测量限制。历史数字不是所有部署环境的性能保证。
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 固件
|
||||
### 1. 无板卡:先看界面
|
||||
|
||||
```sh
|
||||
cp .env.example .env # 填 WIFI_SSID / WIFI_PASS
|
||||
./scripts/flash.sh --monitor 20
|
||||
```
|
||||
|
||||
`flash.sh` 会编译、挑选避开坏字节的 app 偏移、烧录三段(bootloader / 分区表 /
|
||||
app)、打印串口日志。ESP-IDF 位置从 `.cargo/config.toml` 读,不用先 `activate`。
|
||||
|
||||
`.env`:
|
||||
|
||||
```sh
|
||||
WIFI_SSID=你的2.4G网络
|
||||
WIFI_PASS=密码
|
||||
|
||||
# 装甲板(armor/)用这两个里的第一个;队伍号决定灯色与上报里的 team 字段:
|
||||
#ARMOR_TEAM=0 # 0 = 队伍一(红),1 = 队伍二(蓝)
|
||||
|
||||
# 以下都有默认值:
|
||||
#CAM_RES=vga # qqvga | qvga | cif | hvga | vga | svga | xga | sxga | uxga
|
||||
#CAM_QUALITY=12 # 0-63,数字越小画质越好、字节越少
|
||||
#CAM_VSYNC_INVERT=1 # 若板子在硬件上反相了 VSYNC,填 0
|
||||
#WIFI_AUTH=wpa2 # 固定认证方式,仅当扫描看不到 AP 时需要(如隐藏 SSID)
|
||||
```
|
||||
|
||||
凭据由 `env!` 在**编译期**读取,所以没有运行时解析路径,也不会因为忘了读 `.env` 而
|
||||
在板子上带着别人的密码。`ARMOR_TEAM` 同理:换队伍重刷装甲板固件即可,不用擦 NVS。
|
||||
|
||||
`WIFI_AUTH` 一般不用设:固件先扫描 AP,用它报告的真实认证方式去连,只有扫不到时才
|
||||
需要人工指定。指定之后**只**试这一种方式——不成功就直接报错,而不是挨个试 10 秒。
|
||||
挨个试等于在猜一个本该已知的值。
|
||||
|
||||
### 2. 装甲板固件(ESP32-C3)
|
||||
|
||||
```sh
|
||||
scripts/flash_armor.sh # 编译 + 烧录 + 20 秒串口日志
|
||||
python3 scripts/armor_probe.py 15 # 不开界面,单看板子在广播什么血量
|
||||
```
|
||||
|
||||
接线表、血量规则、C3 的 RMT/ADC 踩坑都在 [`armor/README.md`](armor/README.md)。
|
||||
|
||||
### 3. 桌面端
|
||||
安装 Node.js 和 pnpm 后,在仓库根目录执行:
|
||||
|
||||
```sh
|
||||
cd desktop
|
||||
pnpm install
|
||||
pnpm app # = tauri dev
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
没有板子也能把整条链路跑起来:`CAM_DEMO=1 pnpm app` 会合成**协议数据报**喂进真实的接收
|
||||
路径(分片、重组、发现、掉血派生、两道闸门全在跑真代码),界面上标着"演示模式"。
|
||||
打开 [用户端预览](http://127.0.0.1:1420/) 或 [裁判端预览](http://127.0.0.1:1420/?preview=referee)。预览由浏览器合成状态和画面,无需 Rust 后端或板卡。
|
||||
|
||||
首次启动会问这台机器是哪一端:**用户端**要绑一块图传板与一块装甲板(改动需密码),
|
||||
**裁判端**不需要任何配置就能看两路。
|
||||
> [!TIP]
|
||||
> 检查首次启动流程时,可使用 `?preview=first-run` 和 `?preview=password`。需要测试实际接收、重组和录制路径时,使用原生客户端的 `--demo` 模式;两种方式显示的都是合成数据。
|
||||
|
||||
**`desktop/.cargo/config.toml` 里的 `target = "aarch64-apple-darwin"` 换机器必须改。**
|
||||
desktop/ 嵌在固件的目录树里,会继承父目录那个 `xtensa-esp32s3-espidf` 的交叉编译配置,
|
||||
然后在依赖深处以 `libc` 找不到 `MAP_FAILED` 的形式失败。
|
||||
### 2. 连接板卡:运行桌面端
|
||||
|
||||
界面、视频通道、密码模型、录制与排查都在 [`desktop/README.md`](desktop/README.md)。
|
||||
先按[客户端说明](docs/desktop/client.md#快速开始)安装宿主平台依赖。Windows x64 需要 Rust stable MSVC、Visual Studio C++ Build Tools、Windows SDK 和 WebView2;录制另需 ffmpeg。
|
||||
|
||||
## 发现是双向的
|
||||
<details>
|
||||
<summary><strong>Windows x64:开发运行与安装包</strong></summary>
|
||||
|
||||
单靠一个方向建立不了"谁要看我":
|
||||
在 `desktop/` 执行。显式指定目标,并清空从根固件项目继承的 `build-std`:
|
||||
|
||||
- 板子每 **2 秒**广播一次 Hello —— "我在线,我是谁"
|
||||
- 客户端每 **800 ms**广播一次 Subscribe —— "我是这个角色,请给我画面"
|
||||
```powershell
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm app --target x86_64-pc-windows-msvc -- --locked --config 'unstable.build-std=[]'
|
||||
```
|
||||
|
||||
板子从订阅的**源地址**学到推流目标(所以客户端的 socket 必须固定绑 5600 端口,不能
|
||||
用临时端口)。
|
||||
生成 NSIS 安装包:
|
||||
|
||||
对端靠**源地址**记忆,不发探测包。续订本来就每 800 ms 一次,所以"还活着吗"这个问题
|
||||
由续订本身回答了——超时即代表它离开了,多一份心跳状态就多一个能出错的地方。
|
||||
```powershell
|
||||
pnpm tauri build --target x86_64-pc-windows-msvc --bundles nsis --ci -- --locked --config 'unstable.build-std=[]'
|
||||
```
|
||||
|
||||
两端各有自己的超时淘汰,阈值不同:
|
||||
安装包位于 `desktop/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/`。显示名为 **RMIC::2026**,Windows 可执行文件和安装包使用 **RMIC-2026**。
|
||||
|
||||
| | 超时 | 为什么是这个量级 |
|
||||
|---|---|---|
|
||||
| 固件丢弃订阅者 | 6 s | 续订间隔 800 ms,7 倍余量,偶发丢包不会把人踢掉 |
|
||||
| 客户端移除板子 | 60 s | 只影响界面列表,不影响推流 |
|
||||
</details>
|
||||
|
||||
客户端的 60 s 刻意很长:界面列表里留着一块刚重启还没广播的板子,比它短暂消失更可接
|
||||
受。而它每 5 秒才扫一次,不值得为这个上一份状态。
|
||||
<details>
|
||||
<summary><strong>macOS / Linux:目标选择与演示模式</strong></summary>
|
||||
|
||||
固件同时最多向 **4 个**订阅者推流(`MAX_PEERS`)。每个对端每帧要一次 `sendto`,这是帧
|
||||
CPU 成本的倍数;三台客户端(两队 + 裁判)够用,且不让一个陈旧的地址条目拖慢每一帧。
|
||||
`desktop/.cargo/config.toml` 默认目标为 `aarch64-apple-darwin`。Intel Mac 使用 `x86_64-apple-darwin`,常见 Linux x64 使用 `x86_64-unknown-linux-gnu`;其他架构先用 `rustc +stable -vV` 核对 host。
|
||||
|
||||
## 身份:为什么用 MAC 派生
|
||||
|
||||
`protocol::id_from_mac()` 用 FNV-1a 从 MAC 派生 `sender_id`,避开 0(裁判终端的保留
|
||||
值)。
|
||||
|
||||
**用 MAC 而不是 IP**,因为客户端要把绑定存进配置文件并在重启后继续有效——DHCP 换地
|
||||
址是常态,MAC 不会变。用 MAC 也意味着烧同一份固件即可,不需要任何"我是谁"的配置。
|
||||
|
||||
## 线路格式
|
||||
|
||||
27 字节头 + 至多 1400 字节载荷,全部小端。细节在 `protocol/README.md`,那里有完整
|
||||
的字节图和每个拒收条件。
|
||||
|
||||
三处关键决定:
|
||||
|
||||
- **27 字节不凑整。** 头只占每个 1427 字节数据报的 1.9%,而为对齐在两端各写一段
|
||||
手写偏移常量,是引入静默错位的更划算的代价。
|
||||
- **每个数据报 1427 字节,低于 1500 MTU。** 超 MTU 的数据报要么被 AP 丢、要么被 IP
|
||||
分片,两种情况的丢包都记在应用账上而不是网络账上——后者本来更容易排查。
|
||||
- **`version` 不匹配就拒收**,而不是按错误布局继续解析。后者表现为随机崩溃或画面错
|
||||
乱,比拒绝连接难查得多。
|
||||
|
||||
## 硬件
|
||||
|
||||
Freenove ESP32-S3-WROOM CAM,ESP32-S3-WROOM-1-N16R8(16 MB flash + 8 MB octal
|
||||
PSRAM),OV5640。
|
||||
|
||||
### 引脚
|
||||
|
||||
`src/camera.rs` 的 `pins` 模块:
|
||||
|
||||
| | GPIO |
|
||||
|---|---|
|
||||
| RESET | 0 |
|
||||
| XCLK | 15 |
|
||||
| PCLK | 13 |
|
||||
| VSYNC | 6 |
|
||||
| HREF | 7 |
|
||||
| SIOD (SDA) | 4 |
|
||||
| SIOC (SCL) | 5 |
|
||||
| DATA[0..8] | 11, 9, 8, 10, 12, 18, 17, 16 |
|
||||
|
||||
### 千万不要套用别的板子的引脚表
|
||||
|
||||
这块板子的 GPIO 分配是死的:
|
||||
|
||||
| 范围 | 占用 |
|
||||
|---|---|
|
||||
| 26–32 | SPI flash |
|
||||
| 33–37 | octal PSRAM |
|
||||
| 19 / 20 | USB |
|
||||
| 43 / 44 | console |
|
||||
| 45 / 46 | 模组内部 |
|
||||
|
||||
直接套用 AI-Thinker ESP32-CAM 的引脚表会有 **7 根冲突**。冲突的表现不是"某根线不
|
||||
通",而是 `PIN_FUNC_SELECT` 把某个外设接到了 flash 或 PSRAM 上,**CPU 在传输中途挂
|
||||
死**——没有报错、没有回溯、串口直接静默。
|
||||
|
||||
改引脚前先对照上面这张表。
|
||||
|
||||
### 那块坏掉的 flash 字节
|
||||
|
||||
测试用的这块模组在 `0xFADE7` 有个字节的 bit 5–7 **写不进去**。镜像放在哪里会决定哪
|
||||
个镜像字节落到这个地址,落上去就校验失败、bootloader 拒绝启动。
|
||||
|
||||
镜像本身不因位置而变,所以移动 app 分区就够了。`scripts/flash.sh` 会挑最低的 64 KB
|
||||
对齐、且该地址处字节 flash 真的能存的偏移,写回 `partitions.csv`,然后重新编译
|
||||
(重编译会让镜像位移,所以编译后再验证一次)。
|
||||
|
||||
**换到健康硬件**:把 `factory` 分区放回 `0x10000`,删掉 `flash.sh` 里的
|
||||
`BAD_FLASH_ADDR`,别的什么都不用改。
|
||||
|
||||
Flash 跑 DIO @ 40 MHz 而不是这颗片子的标称 QIO/80 MHz:测试模组的坏字节在 quad 模式
|
||||
下会被推到 boot loop。QIO 对**代码执行**速度的提升是明显的(程序从 flash 跑,cache
|
||||
miss 直接打在这条总线上),健康硬件上值得换回去试。
|
||||
|
||||
### `PSRAM DMA` 是故意关掉的
|
||||
|
||||
开着的话,esp32-camera 让 GDMA 直接把帧缓冲写进 PSRAM,然后**只在最后一个 DMA 节点
|
||||
(1 KB)里**找 JPEG 结束标记。标记落在窗口外就丢帧并报 `NO-EOI`——在这块板子上是大部
|
||||
分的帧,几帧之后驱动干脆不再交出任何帧,而且不恢复。
|
||||
|
||||
关掉之后驱动 DMA 到内部 RAM 的中转缓冲,再 memcpy 进 PSRAM。每帧多一次约 23 KB 的
|
||||
memcpy,但搜索整个缓冲找标记。对一条能用的图传链路,这是对的取舍。
|
||||
|
||||
### CPU 频率
|
||||
|
||||
**`CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y`** 是 `sdkconfig.defaults` 里最值钱的一行,
|
||||
而且免费。
|
||||
|
||||
ESP-IDF v5.5 的 Kconfig 把 ESP32-S3 默认成 **160** MHz(见
|
||||
`components/esp_system/port/soc/esp32s3/Kconfig.cpu`),所以默认编译悄悄浪费了一半
|
||||
CPU。相机采集、esp32-camera 里的 JPEG 搜索、lwIP、发送路径全是 CPU 密集的,这一行一
|
||||
次性把它们全部搬上去。
|
||||
|
||||
### WiFi 缓冲留在内部 RAM
|
||||
|
||||
`CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=n`。
|
||||
|
||||
这个值曾经按"PSRAM 很空闲"的直觉写成 `y`,方向恰好相反:WiFi 描述符和 TCP 段**每个
|
||||
包都要碰**,内部 SRAM 比隔着内存总线的 PSRAM 快一个数量级。把它们放进 PSRAM 的表现是
|
||||
高延迟和差吞吐,无论 PSRAM 还剩多少。
|
||||
|
||||
帧缓冲相反——它大、相对少碰,留在 PSRAM。
|
||||
|
||||
### 相机任务钉在 CPU1
|
||||
|
||||
`CONFIG_CAMERA_CORE0=n`(即跑在 CPU1)。WiFi 任务钉在 CPU0。esp32-camera 的采集任务
|
||||
时间是短促且延迟敏感的(DMA 完成、JPEG 标记搜索、拷进帧缓冲),在共享核上会被抢占,
|
||||
每一帧都被拉长。双核芯片上没有理由共享。
|
||||
|
||||
## vendored esp32-camera
|
||||
|
||||
`components/esp32-camera/` 是上游 2.1.7 的副本,因为组件注册表的版本求解器找不到它。
|
||||
|
||||
它打了一处补丁:`target/esp32s3/ll_cam.c` 里的 `CAM_IO_MUX_REG()`。这个宏负责把引脚
|
||||
接到 I2C(OV5640 的 SCCB 走 I2C),不接的话传感器根本不会被发现。
|
||||
|
||||
**注意这个目录叫 `target/`,但它不是构建目录。** 它是上游组件的按芯片分目录
|
||||
(`esp32/`、`esp32s2/`、`esp32s3/`),里面是 `ll_cam.c` 等源码,包含上述补丁。所以
|
||||
`.gitignore` 里三条 `target/` 规则全部锚定在根目录——这是有意的。
|
||||
|
||||
## 改过 C 文件之后
|
||||
以 Linux x64 为例,在 `desktop/` 执行:
|
||||
|
||||
```sh
|
||||
rm -rf target/xtensa-esp32s3-espidf/release/build/esp-idf-sys-*
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm app --target x86_64-unknown-linux-gnu -- --locked --config 'unstable.build-std=[]'
|
||||
```
|
||||
|
||||
不删不会重新编译,你的补丁看起来像完全没生效。
|
||||
同一命令末尾追加 `-- --demo` 可启动原生演示模式。它将合成数据报送入正常接收与重组路径,适合检查截图、录制和命中反馈;预览与演示的区别见[客户端说明](docs/desktop/client.md#演示模式没有板子也能把整条链路跑起来)。
|
||||
|
||||
## 摄像头端不编码,客户端不编码
|
||||
</details>
|
||||
|
||||
OV5640 的 JPEG 编码器**在传感器内部**。DVP 接口出来的一帧要么是 JPEG,要么是 YUV 裸
|
||||
数据。
|
||||
首次启动选择用户端或裁判端。用户端需要设置现场密码并绑定本车板卡;裁判端自动分配图传槽位,装甲板按上报队伍分配。对应关系不符时,在设置中手动指定。
|
||||
|
||||
不编码的话,单帧从约 21 KB 涨到 **450 KB**(21 倍),而 ESP32-S3 **没有硬件 JPEG
|
||||
编码器**(整个 ESP32 系列只有 P4 有,`esp_driver_jpeg` 里
|
||||
`CONFIG_IDF_TARGET_ESP32P4` 是写死的)。
|
||||
### 3. 编译图传或装甲固件
|
||||
|
||||
同理,客户端也不编码:板子出的就是传感器里的 JPEG,客户端只解码。ESP32-S3 也没有硬
|
||||
件 JPEG 解码器。
|
||||
| 硬件 | 构建目标 | 入门文档 |
|
||||
| :--- | :--- | :--- |
|
||||
| Freenove ESP32-S3-WROOM CAM + OV5640 | `xtensa-esp32s3-espidf` | [环境、参数与烧录](docs/setup/firmware.md) |
|
||||
| ESP32-C3 装甲板主板 | `riscv32imc-esp-espidf` | [接线、规则与构建](docs/hardware/armor.md) |
|
||||
|
||||
## 为什么收包和解码要分开线程
|
||||
<details>
|
||||
<summary><strong>已装好图传工具链:构建命令</strong></summary>
|
||||
|
||||
一张 VGA 的 JPEG 解码要几毫秒。放在收包线程里会挤进数据报之间,造成**链路上本不存在
|
||||
的丢帧**——不是真的丢了网络包,是应用来不及取走 socket 缓冲。
|
||||
先按[固件入门](docs/setup/firmware.md)安装固定的 `esp-1.97.0.0` 工具链与 ESP-IDF 依赖。在仓库根目录准备 `.env`;已有文件时不要覆盖。
|
||||
|
||||
现在桌面端把 JPEG **原样**交给 WebView,由浏览器的原生解码器解——后端连解都不解,
|
||||
接收线程只做一次 16 字节头的拼接与一次广播。这一条比上面那条更彻底:省掉的不是几毫秒,
|
||||
而是一整个线程与一次内存拷贝。细节在 [`desktop/README.md`](desktop/README.md)。
|
||||
Windows PowerShell:
|
||||
|
||||
## 排查
|
||||
```powershell
|
||||
if (-not (Test-Path -LiteralPath .env)) {
|
||||
Copy-Item -LiteralPath .env.example -Destination .env
|
||||
}
|
||||
# 用编辑器填写 .env 中的 Wi-Fi 配置后再构建。
|
||||
.\scripts\build.ps1 -Package
|
||||
```
|
||||
|
||||
| 现象 | 原因 |
|
||||
|---|---|
|
||||
| 板子无输出、串口静默 | GPIO 冲突。CPU 在传输中途挂了,见上面那张占用表 |
|
||||
| 几帧后停止出图,报 `NO-EOI` | `PSRAM DMA` 被打开了 |
|
||||
| 板子在线但 `0 clients subscribed` | 订阅包格式不对。`Subscribe` 的 `role` 字节紧跟 27 字节头,在偏移 27 |
|
||||
| 一直接到但画面偶尔解不出来 | 帧重组的问题。**先怀疑你的测量工具**:本项目的探测脚本就因为重组状态没按 `frame_id` 隔离,误报过 34 张坏帧 |
|
||||
| `libc` 找不到 `MAP_FAILED` | `desktop/.cargo/config.toml` 的 target 没改成宿主三元组 |
|
||||
| 串口看不到任何输出 | 非 TTY 下 `espflash monitor` 不可用,用 `/tmp/rd2.py` 裸读 |
|
||||
| WiFi 连不上 | SSID 必须在 2.4GHz。5G 频段看不见 |
|
||||
| 裁判端两块画面宽窄不一 | 两格必须是 `flex: 1 1 0` 且 `min-width: 0`:按内容分宽度时,占位文字长的那一格会多占十几个像素 |
|
||||
| 裁判端两格显示同一块板子 | 旧版槽位分配的真 bug。现在候选里要排除另一个槽位占着的 id |
|
||||
| 有画面但状态栏说"离线" | 视频帧也必须算存活证据(`Discovery::touch`),不能只看 Hello |
|
||||
| 改了绑定却"没生效" | 改动需要先解锁;后端每个改动命令都会校验,界面上的入口只是入口 |
|
||||
Linux / macOS(Bash):
|
||||
|
||||
独立核对线路格式:
|
||||
```bash
|
||||
test -f .env || cp .env.example .env
|
||||
# 用编辑器填写 .env 中的 Wi-Fi 配置后再构建。
|
||||
. "$HOME/export-esp.sh"
|
||||
scripts/build.sh --package
|
||||
```
|
||||
|
||||
上述命令只构建和打包。烧录步骤、引脚与分区偏移请按[固件入门](docs/setup/firmware.md)核对;不要将 ELF 当作 BIN 写入。
|
||||
|
||||
</details>
|
||||
|
||||
Wi-Fi 凭据会编入固件。`.env` 已被 Git 忽略,分享固件前也需检查其中的凭据。
|
||||
|
||||
## 发现与收流时序
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Board as ESP32-S3 图传板
|
||||
participant Client as 桌面 Rust 核心
|
||||
participant View as WebView 画面
|
||||
|
||||
Board-->>Client: Hello 广播(每 2 秒,含版本与协议号)
|
||||
Client->>Board: Subscribe(每 800 ms 续订)
|
||||
Note over Board,Client: 按订阅包源地址推流;6 秒未续订则移除
|
||||
|
||||
loop 采集与发送
|
||||
Board-->>Client: JPEG 分片(27 字节头 + 最多 1400 字节载荷)
|
||||
alt 分片完整且通过接收检查
|
||||
Client->>View: 本地通道投递完整 JPEG
|
||||
View->>View: 解码并绘制最新有效帧
|
||||
else 缺片或超时
|
||||
Client->>Client: 丢弃未完成帧,等待后续帧
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
分片按 `sender_id` 和 `frame_id` 隔离,不重传缺失分片。线路格式、去重和拒收条件见 [UDP 协议](docs/protocol/udp.md)。
|
||||
|
||||
## 仓库结构
|
||||
|
||||
| 路径 | 职责 | 说明 |
|
||||
| :--- | :--- | :--- |
|
||||
| [`src/`](src/) | ESP32-S3 固件 | 采集、分片、发现订阅、发送与重连 |
|
||||
| [`components/`](components/) | C 组件与 FFI | 本地相机驱动、封装及补丁 |
|
||||
| [`protocol/`](protocol/) | 共享线路协议 | 编解码与帧重组,可在主机测试 |
|
||||
| [`desktop/core/`](desktop/core/) | 桌面核心 | 网络、装甲板、配置、密码与槽位 |
|
||||
| [`desktop/src-tauri/`](desktop/src-tauri/) | Tauri 后端 | 本地通道、命令、录制与固件维护 |
|
||||
| [`desktop/src/`](desktop/src/) | React 前端 | 用户端、裁判端、维护与延迟校准 |
|
||||
| [`armor/`](armor/) | ESP32-C3 独立 workspace | 受击规则、灯带与血量广播 |
|
||||
| [`scripts/`](scripts/) | 构建与诊断工具 | 烧录、打包、版本、协议与性能探测 |
|
||||
| [`reference/armor/`](reference/armor/) | 早期 C 版参考工程 | 用于核对硬件与实现差异 |
|
||||
| [`docs/`](docs/index.md) | 项目文档 | 入门、开发、硬件、测试、发布与提案 |
|
||||
|
||||
> [!CAUTION]
|
||||
> `components/esp32-camera/target/` 是按芯片分类的**驱动源码**,包含本地补丁。它不是构建缓存,不能随其他 `target/` 目录一起清理。
|
||||
|
||||
## 开发与验证
|
||||
|
||||
按修改范围运行对应检查。主机 Rust 测试需显式选择宿主目标,避免继承 ESP32 的构建配置;各平台说明见[开发与验证](docs/development/firmware.md)。
|
||||
|
||||
<details>
|
||||
<summary><strong>前端检查与 Windows 主机 Rust 检查</strong></summary>
|
||||
|
||||
前端命令在 `desktop/` 执行:
|
||||
|
||||
```sh
|
||||
python3 scripts/udp_probe.py 10
|
||||
pnpm build
|
||||
pnpm test:stream
|
||||
pnpm test:latency
|
||||
```
|
||||
|
||||
它手写了每一个协议常量,**不依赖 `protocol` crate**——正因为如此,如果 crate 的编码
|
||||
有错,脚本会立刻报出不一致。
|
||||
以下 Rust 命令在仓库根目录执行:
|
||||
|
||||
## 四份文档
|
||||
```powershell
|
||||
cargo +stable test --locked --manifest-path protocol/Cargo.toml --target x86_64-pc-windows-msvc --config 'unstable.build-std=[]'
|
||||
cargo +stable test --locked --manifest-path desktop/core/Cargo.toml --target x86_64-pc-windows-msvc --config 'unstable.build-std=[]'
|
||||
cargo +stable check --locked --manifest-path desktop/src-tauri/Cargo.toml --target x86_64-pc-windows-msvc --config 'unstable.build-std=[]'
|
||||
```
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| 根 `README.md` | 你在这里。拓扑、性能、构建烧录、硬件坑 |
|
||||
| `protocol/README.md` | 线路格式、帧重组的取舍、每个拒收条件 |
|
||||
| `armor/README.md` | 装甲板接线表、血量规则、上报、C3 的 RMT/ADC 踩坑 |
|
||||
| `desktop/README.md` | 两套页面、视频通道路径、两道闸门、密码模型、录制与排查 |
|
||||
录制相关测试会调用 ffmpeg / ffprobe。主机测试、前端构建通过后,仍需按变更范围检查真实 WebView、板卡与网络行为。
|
||||
|
||||
## 装甲板(已实现)
|
||||
</details>
|
||||
|
||||
`armor/` 是一套独立的 Rust workspace(目标 **ESP32-C3**,RISC-V):
|
||||
|
||||
- `armor-protocol` —— 规则层:血量(100 起、每击 −3)、每板独立的 50 ms 受击冷却、
|
||||
受击后**只有那一路**灯带全黑 50 ms、按键 FSM、上报编码。纯计算,主机上 22 个测试。
|
||||
- `armor-firmware` —— 固件:四路压电片 40 kHz ADC 连续采样、四路 WS2812(C3 只有一个
|
||||
RMT 通道可用,靠 `rmt_tx_switch_gpio` 轮流驱动)、NVS、BOOT 键、WiFi + UDP。
|
||||
|
||||
硬件是"一主三从":三块从板里没有 MCU,各自只有一路灯带和一路压电片,直接接线到主板。
|
||||
接线表与实现细节见 [`armor/README.md`](armor/README.md)。
|
||||
提交使用 Conventional Commits,如 `docs(readme): 完善项目入口`。版本由 Git 历史计算,在根目录执行:
|
||||
|
||||
```sh
|
||||
scripts/build_armor.sh # 编译(WIFI_SSID/WIFI_PASS/ARMOR_TEAM 取自 .env)
|
||||
scripts/flash_armor.sh # 编译 + 烧录 + 串口日志
|
||||
python3 scripts/armor_probe.py 15 # 不开界面,单看板子在广播什么血量
|
||||
node scripts/version.mjs --write
|
||||
node scripts/version.mjs --check
|
||||
```
|
||||
|
||||
桌面端侧:`desktop/core/src/armor.rs` 的 `ArmorHealthStore` 监听 5601,按 `(team, index)`
|
||||
存最后一次读数,四条都在 1.5 s 内收到才显示——**没有数据时显示占位符而不是 0**,
|
||||
0 看起来像真值,现场看到会以为对方已经没血了。界面上的血量画在画面角上(一条分 5 格的
|
||||
平行四边形,20 点血一格),归零不写"已击毁/损毁",而是由图传中央的「胜利 / 击败」横幅表达
|
||||
(见 [`desktop/README.md`](desktop/README.md));数字变化也会打进客户端日志。
|
||||
生成的改动记录位于 `docs/releases/`;派生文件清单见[版本维护](docs/development/versioning.md)。
|
||||
|
||||
同一块板子还会被单独记下来(帧头里的 MAC 派生 id),所以两端都能列出"场上有哪几块装甲板"
|
||||
并选一块绑定——命中反馈也是从这个 id 上归位的。**这不需要改固件**:主板一直在用
|
||||
`encode_from(sender_id)` 把自己的 id 打进帧头。
|
||||
## 文档与后续工作
|
||||
|
||||
文档沿用 Firefox 仓库的分类与索引思路:根目录保留项目入口和协作约定,详细说明集中到 [`docs/index.md`](docs/index.md)。
|
||||
|
||||
| 要做什么 | 从哪里开始 |
|
||||
| :--- | :--- |
|
||||
| 首次准备图传板 | [固件入门](docs/setup/firmware.md) → [硬件接线](docs/hardware/camera.md) |
|
||||
| 使用或调试桌面端 | [客户端说明](docs/desktop/client.md) → [故障排查](docs/testing/troubleshooting.md) |
|
||||
| 更新设备固件 | [固件维护](docs/maintenance/firmware.md) |
|
||||
| 修改受击规则或灯带 | [装甲板说明](docs/hardware/armor.md) |
|
||||
| 复核帧率和延迟 | [最新帧记录](docs/testing/realtime.md) → [延迟测量](docs/testing/latency.md) |
|
||||
| 参与开发 | [AGENTS.md](AGENTS.md) → [开发与验证](docs/development/firmware.md) |
|
||||
|
||||
实现状态与待验证事项分开记录:
|
||||
|
||||
- [x] UDP 图传、发现订阅与桌面双模式
|
||||
- [x] 装甲板血量上报、HUD、命中反馈与录制
|
||||
- [x] USB 固件包校验、App 写入与回读
|
||||
- [ ] 完成固件维护的完整实机分区切换验证
|
||||
- [ ] 确认并实现[比赛控制与直播提案](docs/design/match-and-broadcast.md)
|
||||
|
||||
<details>
|
||||
<summary><strong>仓库地址</strong></summary>
|
||||
|
||||
仓库已由 `ESP32S3_vt` 改名为 `simba-rmic-2026`,旧地址会重定向。SSH 地址:
|
||||
|
||||
```text
|
||||
git@git.simba-robotics.com:PRAM/simba-rmic-2026.git
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
+6
-345
@@ -1,352 +1,13 @@
|
||||
# 装甲板(ESP32-C3 / Rust / ESP-IDF)—— 一主三从,与裁判终端联动
|
||||
# 装甲板(ESP32-C3)
|
||||
|
||||
一辆车四块装甲板(前/后/左/右),**只有主板一颗 MCU**:另外三块"从"板里没有单片机,
|
||||
各自只有一路 WS2812 和一路压电片,直接接线到主板上。主板负责四路击打检测、四路灯带、
|
||||
整车血量,并把血量广播给本项目裁判终端。
|
||||
独立的 Rust + ESP-IDF workspace。主板处理四路压电片、四路 WS2812 灯带和整车血量,并经 UDP 向 RMIC::2026 广播。三块从板直接接线到主板,不含 MCU。
|
||||
|
||||
固件是跑在 ESP-IDF 上的 Rust,目标芯片 **ESP32-C3(RISC-V)**。
|
||||
|
||||
```text
|
||||
armor/
|
||||
├── Cargo.toml workspace:两个 crate,各自独立构建
|
||||
├── armor-protocol/ 规则层 + 线路格式(纯计算,主机上 22 个单元测试)
|
||||
├── armor-firmware/ 固件:ADC / RMT / NVS / 按键 / WiFi+UDP
|
||||
│ ├── src/leds.rs 四路 WS2812(单 RMT 通道轮流切 GPIO)
|
||||
│ ├── src/piezo.rs 四路压电片(40 kHz ADC 连续采样)
|
||||
│ ├── src/net.rs UDP 广播 ArmorHealth
|
||||
│ ├── src/settings.rs NVS:队伍与颜色
|
||||
│ └── sdkconfig.defaults / partitions.csv
|
||||
└── .cargo/config.toml riscv32imc-esp-espidf + MCU=esp32c3
|
||||
```
|
||||
|
||||
**为什么拆成两个 crate。** 规则层(算不算一次受击、扣几滴血、这一刻灯该显示什么、
|
||||
按一下键算短按还是长按)是最容易写错、又最不适合"烧上去看现象"的部分 —— 压电片的
|
||||
尖峰在硬件上根本没法按需复现。放在 `armor-protocol` 里就能喂 `Duration` 假时间来测。
|
||||
固件侧只做外设接线。
|
||||
|
||||
## 接线
|
||||
|
||||
| 功能 | GPIO | 说明 |
|
||||
| --- | --- | --- |
|
||||
| WS2812 前 | **IO10** | 每条灯带 3 颗灯珠,GRB、MSB first |
|
||||
| WS2812 后 | **IO5** | |
|
||||
| WS2812 左 | **IO6** | |
|
||||
| WS2812 右 | **IO7** | |
|
||||
| 压电片 前 | GPIO0(ADC1_CH0) | 0 dB 衰减、12 bit |
|
||||
| 压电片 后 | GPIO4(ADC1_CH4) | |
|
||||
| 压电片 左 | GPIO3(ADC1_CH3) | |
|
||||
| 压电片 右 | GPIO1(ADC1_CH1) | |
|
||||
| BOOT 键 | GPIO9 | 低有效,内部上拉 |
|
||||
| 调试串口 UART0 TXD | GPIO21 | 115200 8N1,日志从这里出来(主控制台) |
|
||||
| 调试串口 UART0 RXD | GPIO20 | 目前空闲,留给以后的命令通道 |
|
||||
|
||||
引脚与参考实现完全一致(灯:前 10 / 后 5 / 左 6 / 右 7;压电:前 0 / 后 4 / 左 3 /
|
||||
右 1)。调试口用 UART0 而不是 USB Serial/JTAG,是为了让"RXD/TXD"这对丝印就是日志口。
|
||||
|
||||
**日志同时从板上原生 USB 口出来。** `sdkconfig.defaults` 里把 USB Serial/JTAG 配成了
|
||||
**次**控制台(`CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG=y`):主控制台仍是 UART0,
|
||||
所以"RXD/TXD 就是调试口"这条成立;而只用原生 USB 线烧录、没接 USB-TTL 的人也能看到日志,
|
||||
否则那种接法下应用日志一个字符都看不到(只有 ROM 的启动信息)。
|
||||
|
||||
## 规则
|
||||
|
||||
| | 值 | 在哪 |
|
||||
| --- | --- | --- |
|
||||
| 整车血量 | 100 | `Rules::max_hp` |
|
||||
| 每次受击扣血 | 3 | `Rules::damage_per_hit` |
|
||||
| 受击后变暗 | **只有被打的那一路**全黑 50 ms,其余三路不动 | `Rules::hit_blink` + `Armor::strip` |
|
||||
| 同一板位重复判定抑制 | 50 ms | `Rules::hit_cooldown` |
|
||||
| 受击阈值 | 原始 12 bit ADC > 2000(`ARMOR_THRESHOLD` 可改) | `Rules::threshold` |
|
||||
| 血量归零 | 四路全黑,直到复位 | `Armor::strip` |
|
||||
| 正常颜色 | 队伍一红(`0xFF0000`)、队伍二蓝(`0x0000FF`) | `Rgb::for_team` |
|
||||
| 复位(新一局) | 长按 BOOT ≥500 ms → 满血、清计数 | `Button` + `Armor::reset` |
|
||||
| 打印状态 | 短按 BOOT → 立刻打一行状态并重发上报 | `ButtonEvent::Click` |
|
||||
|
||||
**整车一条血量,四格同值。** 板子算账、裁判端只显示:每条上报的 `hp` 相同,
|
||||
`index` 分别是 0..3,所以裁判端已经画好的四格血量界面不用改。
|
||||
|
||||
**为什么要"每板独立冷却"。** 参考实现用全局 50 ms 冷却:一块板挨打后,别块板在这
|
||||
50 ms 内的击打会被吃掉。四路各自独立才是"每面各自挨打"的正确语义,这里有单元测试
|
||||
钉住(`cooldown_is_per_plate_not_global`)。
|
||||
|
||||
## 上报(UDP)
|
||||
|
||||
主板每 200 ms 广播四条 `protocol::ArmorHealth` 到 `255.255.255.255:5601`:
|
||||
|
||||
```text
|
||||
magic "MVC1"(4) | type(1)=5 | version(2) | sender_id(4) | ... | team(1) | index(1) | hp(2)
|
||||
```
|
||||
|
||||
- **血量一变立刻发**,没变就按 200 ms 心跳重发。裁判端靠心跳判断板子还在线,一次
|
||||
丢包不该让界面退回"未接入"。
|
||||
- `sender_id` 用 MAC 派生的板子 id(`protocol::id_from_mac`),不是 0 —— 0 是裁判
|
||||
终端的保留 id,抓包时一块装甲板发的包不该长得像裁判端发的。
|
||||
- 四条 `index` 的 `hp` 相同(整车一条血),裁判端按 `(team, index)` 收齐四条才显示。
|
||||
- 广播而不是单播:裁判终端的 IP 是 DHCP 给的,会变。图传板用 Hello 广播绕开这件事,
|
||||
装甲板沿用同一个约定。
|
||||
|
||||
### 裁判端怎么收
|
||||
|
||||
`client/src/armor.rs` 里的 `ArmorHealthStore`(裁判端角色才启动):
|
||||
|
||||
- 绑 `0.0.0.0:5601` 收广播,按 `(team, index)` 存最后一次读数。
|
||||
- **四条都在 1.5 s 内收到**才交给界面显示;否则回退占位符。心跳 200 ms,1.5 s 是连丢
|
||||
7 个包,偶发丢包不会让四个格子各自跳。
|
||||
- 队伍端不用它(界面不显示对手血量),所以那台机器不会去占 5601。
|
||||
- 血量变化会打日志(`armor: team 0 hp 100 -> 97`):窗口是给别人看的,抓日志的人
|
||||
也得能看见掉血。
|
||||
|
||||
**裁判端界面原本看不到血量。** 协议与 `ArmorSource` 早已就位,但 Slint 里那 4 格只是
|
||||
绿/灰方块 —— `hps` 的数值从没被渲染,于是"板子被打到 0"在界面上完全看不出来(现场
|
||||
表现就是"敲死了裁判系统没掉血")。现在 `TeamHealth` 多了一个 `hp-text` 与 `critical`:
|
||||
队伍名旁边直接显示血量数字,低于 30 或归零时数字与血条转红。归零**不写"已击毁"**——那一刻
|
||||
图传中央会给出「胜利 / 击败」的结论(见 [`desktop/README.md`](../desktop/README.md))。
|
||||
|
||||
## 构建
|
||||
|
||||
凭据与队伍号走 `.env`(与图传固件共用同一个文件):
|
||||
|
||||
```bash
|
||||
# .env
|
||||
WIFI_SSID=your-ssid
|
||||
WIFI_PASS=your-password
|
||||
ARMOR_TEAM=0 # 0 = 队伍一(红),1 = 队伍二(蓝)
|
||||
ARMOR_THRESHOLD=2000 # 受击阈值(原始 12 bit ADC),看日志里的 peak 来定
|
||||
|
||||
scripts/build_armor.sh # 编译
|
||||
ARMOR_TEAM=1 scripts/build_armor.sh # 临时当队伍二编译
|
||||
ARMOR_THRESHOLD=1200 scripts/build_armor.sh # 换受击阈值(看日志里的 peak 来定)
|
||||
```
|
||||
|
||||
产物:`armor/target/riscv32imc-esp-espidf/release/armor`。
|
||||
|
||||
`ARMOR_TEAM` 是**编译期**注入的:换队伍重刷固件即可,不用先擦 NVS。NVS 里另外记着
|
||||
`team` 与 `color`(命名空间 `armor`,与参考实现同名),两者不一致时以 `ARMOR_TEAM`
|
||||
为准并覆盖,日志里会说一声。
|
||||
|
||||
本机已有的 ESP-IDF 与工具链通过 `armor/.cargo/config.toml` 指定(`IDF_PATH` /
|
||||
`ESP_IDF_TOOLS_INSTALL_DIR`),不需要先 `activate` ESP-IDF。删掉那两行就交给
|
||||
esp-idf-sys 自己下载它钉住的版本。
|
||||
|
||||
### 用的是 ESP-IDF v6.0.1,不是图传板那套 v5.5.5
|
||||
|
||||
这台机器上有两套 ESP-IDF:
|
||||
|
||||
| | 路径 | 工具链 |
|
||||
| --- | --- | --- |
|
||||
| v6.0.1(eim 装的) | `/Users/peng/.espressif/v6.0.1/esp-idf` | Xtensa + **RISC-V(`esp-15.2.0_20251204`)** |
|
||||
| v5.5.5(图传板在用) | `/Users/peng/.espressif/esp-idf/v5.5.5` | 只有 Xtensa |
|
||||
|
||||
C3 是 **RISC-V**,而 v5.5.5 那套缺 `riscv32-esp-elf`(它要的是 `esp-14.2.0_20260121`,
|
||||
本机没有,而 GitHub release 又下不动)。所以装甲板固件指向 v6.0.1 —— 它的工具链齐全,
|
||||
且 `esp-idf-sys` 0.38.1 支持的版本范围是 4.4.4+、5.0–5.5、**6.0–6.2**。
|
||||
|
||||
两套 IDF 共存没有冲突:各自 `.cargo/config.toml` 里的 `IDF_PATH` 是每套 crate 自己钉的。
|
||||
|
||||
`ESP_IDF_TOOLS_INSTALL_DIR` 默认 `global`(即 `~/.espressif`,工具所在处),但**没有**加
|
||||
`force`:沙箱或 CI 里可以用 `ESP_IDF_TOOLS_INSTALL_DIR=custom:<可写目录>` 覆盖(工具目录
|
||||
不可写时把 `tools/` 软链到已装好的工具即可,Python venv 会建在那个可写目录里)。
|
||||
|
||||
### 主机侧测试
|
||||
|
||||
规则层在 PC 上跑,不需要板子:
|
||||
|
||||
```bash
|
||||
cd armor/armor-protocol && cargo test # 22 个测试
|
||||
cd protocol && cargo test # 线路格式往返(含 ArmorHealth)
|
||||
```
|
||||
|
||||
(`armor-protocol/.cargo/config.toml` 把 target 覆盖回 host:它坐在 `armor/` 目录树
|
||||
里,否则会继承 RISC-V 目标,链接必然失败。这与 `protocol/`、`client/` 的做法一致。)
|
||||
|
||||
## 烧录与实测
|
||||
|
||||
```bash
|
||||
scripts/flash_armor.sh # 自动找串口,烧完看 20 秒日志
|
||||
ESP_PORT=/dev/cu.usbmodem1101 scripts/flash_armor.sh
|
||||
scripts/flash_armor.sh --monitor 60 # 看 60 秒
|
||||
scripts/flash_armor.sh --no-monitor # 只烧不抓
|
||||
```
|
||||
|
||||
烧的是 ELF(`espflash` 自己从 ELF 里读分区表与 bootloader),芯片 `esp32c3`。
|
||||
|
||||
不打开裁判端界面,也可以单独确认板子在广播、血量对不对:
|
||||
从仓库根目录执行:
|
||||
|
||||
```bash
|
||||
scripts/build_armor.sh
|
||||
scripts/flash_armor.sh
|
||||
python3 scripts/armor_probe.py 15
|
||||
```
|
||||
|
||||
它手写协议常量(不依赖 Rust 侧代码),所以是线路格式的**独立**检查:如果固件编码写错,
|
||||
这个脚本会立刻报不一致。
|
||||
|
||||
### 串口日志长什么样(实机抓的)
|
||||
|
||||
```text
|
||||
I (321) armor: pins: ws2812 front=IO10 rear=IO5 left=IO6 right=IO7
|
||||
I (321) armor: pins: piezo front=GPIO0 rear=GPIO4 left=GPIO3 right=GPIO1
|
||||
I (4864) esp_netif_handlers: sta ip: 192.168.1.117, mask: 255.255.255.0, gw: 192.168.1.1
|
||||
I (5034) armor: joined "SR-602-2G4" on 192.168.1.117
|
||||
I (5034) armor::net: reporting to 255.255.255.255:5601 over UDP
|
||||
I (5034) armor::net: this board's id is 0xA820D321, from MAC [98, A3, 16, 4C, 67, C8]
|
||||
I (6044) armor: status: hp=100 hits=0 dead=false plates[front=0 rear=0 left=0 right=0]
|
||||
peak[front=490 rear=395 left=456 right=475] thr=2000
|
||||
strips[front=#FF0000 rear=#FF0000 left=#FF0000 right=#FF0000]
|
||||
ap=SR-602-2G4 rssi=-36 ch=6 ip=192.168.1.117 reports=5 errs=0
|
||||
I (63104) armor: hit plate=front raw=4095 hp=1 (hits=33)
|
||||
I (63314) armor: hit plate=front raw=4095 hp=0 (hits=34)
|
||||
W (63314) armor: hp reached 0: all four strips stay dark until the next reset
|
||||
W (306504) armor: BOOT long press: hp reset to 100 (new round)
|
||||
```
|
||||
|
||||
(日志里的长行是这里折行显示的,实机是一行。)
|
||||
|
||||
## 实测(2026-10-14,ESP32-C3 devkit,MAC `98:a3:16:4c:67:c8`)
|
||||
|
||||
| 项 | 结果 |
|
||||
| --- | --- |
|
||||
| 编译 | `cargo build --release` 通过;text 1,081,093 B,app 镜像 **1,082,848 B**,占 4 MB flash 的 26% |
|
||||
| 启动 | 无 panic/重启;状态行每秒一条 |
|
||||
| WiFi | 关联 `SR-602-2G4`(rssi −36,ch 6),DHCP 拿到 `192.168.1.117` |
|
||||
| 上报 | `scripts/armor_probe.py` 收到队伍一四条、约 5 包/秒、`sender_id 0xA820D321`、四格同值 |
|
||||
| 裁判端 | `cam-client --role referee` 日志 `team 0 plate 0..3 is online (hp 100) via 192.168.1.117`,界面显示血量数字 |
|
||||
| 受击 | 敲前装甲板:`hit plate=front raw=2663..4095`,每次 −3(100→97→94…),只有 front 计数 |
|
||||
| 变暗 | 敲击瞬间**只有前那一路**全黑 50 ms,其余不变(现场确认) |
|
||||
| 死亡 | 34 击到 0;状态行 `dead=true strips[front=off rear=off left=off right=off]`,四路全黑 |
|
||||
| 复位 | BOOT 长按 → 板子 `reset to 100`,裁判端日志 `armor: team 0 hp 0 -> 100` |
|
||||
| 底噪 | 未敲击时四路 peak 约 380–510,阈值 2000 有明显余量 |
|
||||
| 未接线 | 后/左/右三路压电片与灯带本次未接(用户说明),代码按四路齐全写 |
|
||||
|
||||
### 裁判端这边踩到的坑:界面根本不显示血量
|
||||
|
||||
协议、`ArmorSource`、`ArmorHealthStore` 都对,但改之前 Slint 里那 4 格只是绿/灰方块,
|
||||
`hps` 的**数值从不渲染**——板子被打到 0,界面四格还是绿的,看起来像"没掉血"。现在
|
||||
队伍名旁边直接显示数字(低于 30 转红,归零不写"已击毁",改由图传中央的胜负横幅表达),
|
||||
并且客户端日志会打 `armor: team 0 hp 100 -> 97`。
|
||||
|
||||
## 实现要点(都是踩过的坑)
|
||||
|
||||
### 1. ESP32-C3 的 RMT 只有 96 个 symbol 的通道内存
|
||||
|
||||
C3 的 RMT 只有一个 block,总计 96 个 symbol,按 48 为单位分给两个 TX 通道。`mem_block_symbols = 64`
|
||||
会被向上取整到 96,也就是一个通道吃光全部内存,第二个 `rmt_new_tx_channel()` 就会失败:
|
||||
|
||||
```text
|
||||
E rmt: rmt_tx_register_to_group(168): no free tx channels
|
||||
```
|
||||
|
||||
四条灯带不可能各占一个通道(只有 2 个 TX 通道)。所以这里只建**一个**通道、让它独占
|
||||
96 个 symbol(一帧 = 3 珠 × 24 bit + reset = 73 个 symbol,装得下),再用
|
||||
`rmt_tx_switch_gpio()` 依次切到四个 GPIO 上发送;每路约 90 µs。
|
||||
|
||||
**为什么不用 `esp-idf-hal` 的 `TxChannelDriver`。** 它把 `rmt_channel_handle_t` 藏在
|
||||
结构体里(`as_ptr()` 是 `pub(crate)`),拿不到句柄就调不了 `rmt_tx_switch_gpio()`。
|
||||
所以 `leds.rs` 直接用 sys 绑定:通道、官方 copy encoder、transmit 都是 IDF 原生的那
|
||||
几个函数,用法与参考实现的 `led_strip_encoder.c` 一一对应。
|
||||
|
||||
**每路内容没变就不重发。** 参考实现里四条灯带永远同色,所以每 10 ms 无条件刷四遍;
|
||||
这里四路颜色独立(被打的那一路要单独变暗),于是只在内容变化时才走
|
||||
`disable → switch → enable → transmit`,另外每秒强制全刷一次(掉电、静电都可能让
|
||||
某条灯带丢帧,缓存不能当真相)。
|
||||
|
||||
### 2. 压电片必须连续(DMA)采样
|
||||
|
||||
一次击打的波形只有几十微秒宽,oneshot 轮询几乎必然从尖峰旁边擦过去,表现就是"敲得响
|
||||
才算"。这里与参考实现一致:四通道合计 40 kHz(每通道 10 kHz),一帧 10 ms = 400 个
|
||||
采样点,一帧就是主循环的节拍。
|
||||
|
||||
`esp-idf-hal` 0.47 的 `adc::continuous` 多通道入口 `EmptyAdcChannels::chain` 的约束是
|
||||
`A: Adc` 且 `O: AdcChannels<AdcUnit = A>`:`ADC1` 满足前者、`ADCU1` 满足后者,同一个
|
||||
类型参数凑不齐,四路 ADC1 通道拼不出来。所以 `piezo.rs` 同样直接用 sys 绑定,与参考
|
||||
实现逐行对应。
|
||||
|
||||
采样点的通道号由硬件结果自带(`type2.channel()`),映射不依赖"结果按 pattern 顺序
|
||||
轮转"这个假设。
|
||||
|
||||
### 3. 一次击打会被算成几十次
|
||||
|
||||
40 kHz 下,压电片一次击打会连续几十个采样点过阈值。**去抖不在采样层做**,而是
|
||||
`Armor::on_sample` 的冷却窗口把它收敛成一次受击 —— 这样"算不算受击"这条规则可以在
|
||||
主机上测(`repeat_samples_inside_the_cooldown_are_one_hit`)。
|
||||
|
||||
### 4. 控制台放在 UART0
|
||||
|
||||
`sdkconfig.defaults` 里显式写了 `CONFIG_ESP_CONSOLE_UART_DEFAULT=y`:C3 的 ROM 默认
|
||||
控制台就是 UART0,引脚正好是 RXD=GPIO20 / TXD=GPIO21,与要求一致。换成 USB
|
||||
Serial/JTAG 输出只要把这一行换成 `CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG`。
|
||||
|
||||
### 5. 应用分区给 2 MB
|
||||
|
||||
默认的 1 MB 单 app 槽对带 WiFi 的 Rust 固件太紧,`partitions.csv` 按参考实现给
|
||||
factory 划了 2 MB。
|
||||
|
||||
### 6. WiFi 省电关掉
|
||||
|
||||
省电模式下上报会晚一个信标间隔(上百毫秒),而"受击后 50 ms 变暗"本来就是要给人即时
|
||||
反馈的。板子是车载供电,这点电流不值得换不确定性。另外主循环里每 5 s 检查一次连接,
|
||||
断了就重连 —— 一块静默失联的装甲板在场上是最难查的故障。
|
||||
|
||||
### 7. 判断"连上没有"要问 IDF,不要信 esp-idf-svc 的事件缓存
|
||||
|
||||
`BlockingWifi::is_connected()/is_up()/wait_netif_up()` 读的是 esp-idf-svc 自己维护的
|
||||
WiFi 事件状态。实测在 **IDF v6.0.1 + esp-idf-svc 0.53** 上它会停在"已连接":日志说
|
||||
一切正常、`get_ip_info()` 却是 `0.0.0.0`,而 IDF 的 `esp_wifi_sta_get_ap_info()` 明明
|
||||
说没关联。后果是 `join()` 假装连上了、看门狗永远不会重试,板子静默失联。
|
||||
|
||||
所以固件里一律用 IDF 的 API 判断:
|
||||
|
||||
- 关联:`esp_wifi_sta_get_ap_info()`
|
||||
- 地址:`esp_netif_get_handle_from_ifkey("WIFI_STA_DEF")` + `esp_netif_get_ip_info()`
|
||||
|
||||
而且 **WiFi 是尽力而为**:`join()` 失败只警告、不退出。一块因为没网就不亮灯的装甲板,
|
||||
比"裁判端看不到血量"糟糕得多。看门狗每 5 s 检查一次"关联 + 有 IP",不满足就断开重连
|
||||
(重连时只等 3 s IP,别让主循环错过受击)。
|
||||
|
||||
### 8. 日志里"有没有连上"要说清楚
|
||||
|
||||
状态行长这样:
|
||||
|
||||
```text
|
||||
status: hp=0 hits=37 dead=true plates[front=37 rear=0 left=0 right=0]
|
||||
peak[front=504 rear=379 left=459 right=486] thr=2000
|
||||
strips[front=off rear=off left=off right=off]
|
||||
ap=SR-602-2G4 rssi=-29 ch=6 ip=192.168.1.117 reports=1745 errs=0
|
||||
```
|
||||
|
||||
- `peak[...]` 是**这一秒内每路 ADC 的峰值**,用来校准阈值:没敲时的底噪要明显低于
|
||||
阈值,正常敲击要明显高于它(实测底噪 ~400–500,敲击 2000–4095)。只报"有没有过阈值"
|
||||
没法判断阈值该调到哪。
|
||||
- `thr=` 是当前阈值,`ARMOR_THRESHOLD=1200 scripts/build_armor.sh` 可以改。
|
||||
- `ap=` / `ip=` / `reports=` 一眼分清"板子没连上"和"包发出去了但对端没显示"。
|
||||
|
||||
### 9. USB 控制台看不到复位后的头两秒
|
||||
|
||||
主控制台是 UART0,另外把 USB Serial/JTAG 配成了次控制台,所以插原生 USB 也能看日志。
|
||||
但复位时 USB 会重新枚举,**复位后最初约 1–2 秒的日志会丢**(我们实测错过过启动横幅)。
|
||||
要看完整启动过程就用 UART0 那对脚接 USB-TTL——那才是设计中的调试口。
|
||||
|
||||
## 与参考实现(`reference/armor`)的差异
|
||||
|
||||
| | 参考实现 | 这里 | 为什么 |
|
||||
| --- | --- | --- | --- |
|
||||
| 主从链路 | ESP-NOW 配对,两块板对拷 | 无:三块从板只是直接接线的灯与传感器 | 按实际硬件(只有主板一颗 MCU) |
|
||||
| DBUS 透传 | UART0 收发 10 字节 DBUS,USB CDC 打 CSV | 不做 | 这套硬件没有 DBUS 需求;UART0 让给日志 |
|
||||
| 受击变暗 | 四条灯带一起全黑 50 ms | 只暗被打的那一路 | 每面独立灯带的意义就在这里 |
|
||||
| 冷却窗口 | 全局 50 ms | 每板独立 50 ms | 别块板的击打不该被吃掉 |
|
||||
| 灯带刷新 | 每 10 ms 无条件刷四遍 | 只在内容变化时刷 + 每秒强制全刷 | 四路独立控制后,省掉四轮 RMT 开销 |
|
||||
| 血量 | 只有击打计数,无血量 | 整车 100 血、每击 −3、0 血全黑,按 `ArmorHealth` 上报 | 与裁判端已画好的四格血量对齐 |
|
||||
| 颜色切换 | BOOT 短按切红/蓝,存 NVS | 颜色由 `ARMOR_TEAM` 决定(红/蓝),短按打状态 | 队别不该靠现场按键切错 |
|
||||
| 按键 | 移植 OneButton 状态机(短按配对,长按切色) | 本地 FSM:长按复位血量、短按打状态 | 只有两种动作,且要能在主机上测 |
|
||||
| 上报 sender_id | —(不走 UDP) | MAC 派生 id | 0 是裁判终端保留 id |
|
||||
|
||||
## 还没做的
|
||||
|
||||
- **另外三路(后/左/右)的压电片与灯带还没接**(2026-10-14 实机只有前一路)。
|
||||
代码按四路齐全写,接线后无需改代码;但"四路同时受击"、"每路独立变暗"这两件事
|
||||
还没在实板上验证过。
|
||||
- **UART0 RX(GPIO20)上的命令通道**:日志方向已经能用;反向的 `status` / `reset`
|
||||
命令还没接(当前复位走 BOOT 长按)。
|
||||
- **从板加 MCU 的方案**:如果以后从板也装单片机(走 UART 或无线回传),
|
||||
`armor-protocol` 里已经预留了板位枚举与上报格式,加一个 `slave` bin 即可。
|
||||
- **双击 / 多击**:`Button` 只实现短按与长按,参考实现里未使用的 OneButton 功能没有移植。
|
||||
- **下发血量**:当前板子本地算账(裁判端只显示)。若以后要裁判端权威(下发血量/复位),
|
||||
需要新增一条下行消息并在 `client` 里发。
|
||||
接线、规则、工具链版本、测试记录与实现限制见[装甲板说明](../docs/hardware/armor.md)。它使用 ESP32-C3 / RISC-V 目标,不能烧录 S3 图传固件。其他主题见[文档首页](../docs/index.md)。
|
||||
|
||||
@@ -1,3 +1,79 @@
|
||||
// ESP-IDF 的环境导出 + 固件版本信息注入。
|
||||
//
|
||||
// # 版本号为什么在这里注入
|
||||
//
|
||||
// 固件的版本必须和客户端、和 `version.json` 是**同一个数**。手工在 `src/` 里写一个
|
||||
// 常量,第一次忘了改就分叉了,而且没有任何东西会报错——现场只会表现为"界面说固件 0.2.0,
|
||||
// 实际烧的是别的"。
|
||||
//
|
||||
// 所以这里在编译期把 `version.json` 读进来,导出成 `cargo:rustc-env`,源码里用
|
||||
// `env!()` 取。`version.json` 由 `scripts/version.mjs` 从 git 历史算出来,于是
|
||||
// 一条链是通的:git → version.json → 固件二进制 → Hello 报文 → 客户端界面。
|
||||
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
fn main() {
|
||||
embuild::espidf::sysenv::output();
|
||||
emit_version_env();
|
||||
}
|
||||
|
||||
/// 把 `version.json` 里的版本拆成几个编译期常量。
|
||||
fn emit_version_env() {
|
||||
let manifest = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR");
|
||||
let path = Path::new(&manifest).join("version.json");
|
||||
|
||||
// 这个文件改了要重新编译,否则版本号会停在旧值上而二进制看起来是新的。
|
||||
println!("cargo:rerun-if-changed={}", path.display());
|
||||
|
||||
// 没有 version.json 也要能编:直接 cargo build 的人可能还没跑过生成脚本。
|
||||
// 这时退回 0.0.0+0 并留一条编译器警告——比编不过友好,比编出一个假的版本诚实。
|
||||
let (major, minor, patch, build) = match fs::read_to_string(&path) {
|
||||
Ok(text) => match parse(&text) {
|
||||
Some(parsed) => parsed,
|
||||
None => {
|
||||
println!("cargo:warning=version.json 解析失败,固件版本回退为 0.0.0+0");
|
||||
(0, 0, 0, 0)
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
println!(
|
||||
"cargo:warning=找不到 version.json,固件版本回退为 0.0.0+0(先跑 node scripts/version.mjs --write)"
|
||||
);
|
||||
(0, 0, 0, 0)
|
||||
}
|
||||
};
|
||||
|
||||
let packed = ((major as u32) << 16) | (((minor as u32) & 0xff) << 8) | ((patch as u32) & 0xff);
|
||||
|
||||
println!("cargo:rustc-env=RMIC_VERSION={major}.{minor}.{patch}");
|
||||
println!("cargo:rustc-env=RMIC_VERSION_PACKED={packed}");
|
||||
println!("cargo:rustc-env=RMIC_BUILD={build}");
|
||||
}
|
||||
|
||||
/// 从 `version.json` 里抠出四段数字。
|
||||
///
|
||||
/// 刻意不引入 `serde_json`:解析四个字段不值得多一个编译期依赖,而固件的构建时间本来
|
||||
/// 就是这个项目里最贵的东西。
|
||||
fn parse(text: &str) -> Option<(u64, u64, u64, u64)> {
|
||||
let field = |name: &str| -> Option<u64> {
|
||||
let key = format!("\"{name}\"");
|
||||
let at = text.find(&key)?;
|
||||
let rest = &text[at + key.len()..];
|
||||
let colon = rest.find(':')?;
|
||||
let rest = &rest[colon + 1..];
|
||||
let start = rest.find(|c: char| c.is_ascii_digit())?;
|
||||
let digits: String = rest[start..]
|
||||
.chars()
|
||||
.take_while(|c| c.is_ascii_digit())
|
||||
.collect();
|
||||
digits.parse().ok()
|
||||
};
|
||||
|
||||
Some((
|
||||
field("major")?,
|
||||
field("minor")?,
|
||||
field("patch")?,
|
||||
field("build")?,
|
||||
))
|
||||
}
|
||||
@@ -2,4 +2,4 @@ idf_component_register(SRCS "cam_wrapper.c"
|
||||
INCLUDE_DIRS "include"
|
||||
# esp_timer for the frame-rate probe; the camera component
|
||||
# pulls in the LCD_CAM driver and the FreeRTOS task API.
|
||||
REQUIRES esp32-camera esp_timer)
|
||||
REQUIRES esp32-camera esp_timer esp_driver_tsens)
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
/* Implementation of the cam_wrapper shim around esp32-camera. See cam_wrapper.h. */
|
||||
|
||||
#include "cam_wrapper.h"
|
||||
#include "driver/temperature_sensor.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
|
||||
static void cam_wrapper_measure_fps(cam_wrapper_sensor_t sensor);
|
||||
static float cam_wrapper_measure_fps(cam_wrapper_sensor_t sensor);
|
||||
|
||||
static const char *TAG = "cam_wrapper";
|
||||
|
||||
static sensor_t *s_sensor;
|
||||
static char s_sensor_name[16] = "unknown";
|
||||
static temperature_sensor_handle_t s_temperature;
|
||||
|
||||
static void fill_config(const cam_wrapper_config_t *in, camera_config_t *out)
|
||||
{
|
||||
@@ -134,7 +136,7 @@ esp_err_t cam_wrapper_init(const cam_wrapper_config_t *config, cam_wrapper_senso
|
||||
* two is dropping frames. */
|
||||
#define PROBE_FRAMES 20
|
||||
|
||||
static void cam_wrapper_measure_fps(cam_wrapper_sensor_t sensor)
|
||||
static float cam_wrapper_measure_fps(cam_wrapper_sensor_t sensor)
|
||||
{
|
||||
uint32_t total = 0;
|
||||
int64_t start = esp_timer_get_time();
|
||||
@@ -158,7 +160,7 @@ static void cam_wrapper_measure_fps(cam_wrapper_sensor_t sensor)
|
||||
|
||||
if (taken == 0 || elapsed_us <= 0) {
|
||||
ESP_LOGW(TAG, "fps probe: got no frames");
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
double fps = (double)taken * 1000000.0 / (double)elapsed_us;
|
||||
@@ -167,6 +169,159 @@ static void cam_wrapper_measure_fps(cam_wrapper_sensor_t sensor)
|
||||
"(sensor ceiling, no network involved)",
|
||||
taken, (long long)(elapsed_us / 1000), fps,
|
||||
(unsigned)(total / (uint32_t)taken));
|
||||
return (float)fps;
|
||||
}
|
||||
|
||||
esp_err_t cam_wrapper_set_grayscale(int enabled)
|
||||
{
|
||||
if (!s_sensor || !s_sensor->set_special_effect) return ESP_ERR_NOT_SUPPORTED;
|
||||
int rc = s_sensor->set_special_effect(s_sensor, enabled ? 2 : 0);
|
||||
if (rc != 0) return ESP_FAIL;
|
||||
ESP_LOGI(TAG, "sensor JPEG colour: %s", enabled ? "grayscale" : "colour");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t cam_wrapper_set_quality(unsigned quality)
|
||||
{
|
||||
if (quality > 63) return ESP_ERR_INVALID_ARG;
|
||||
if (!s_sensor || !s_sensor->set_quality) return ESP_ERR_NOT_SUPPORTED;
|
||||
return s_sensor->set_quality(s_sensor, (int)quality) == 0 ? ESP_OK : ESP_FAIL;
|
||||
}
|
||||
|
||||
esp_err_t cam_wrapper_set_exposure_bias(int ev)
|
||||
{
|
||||
if (ev < -2 || ev > 0) return ESP_ERR_INVALID_ARG;
|
||||
if (!s_sensor || s_sensor->id.PID != OV5640_PID)
|
||||
return ev == 0 ? ESP_OK : ESP_ERR_NOT_SUPPORTED;
|
||||
/* Driver target = (level+5)*10+5, default 55. Targets 25 and 15
|
||||
* are ~-1.14 and ~-1.87 EV, not literal driver levels -1 and -2. */
|
||||
int level = ev == -1 ? -3 : ev == -2 ? -4 : 0;
|
||||
if (s_sensor->set_exposure_ctrl(s_sensor, 1) != 0
|
||||
|| s_sensor->set_gain_ctrl(s_sensor, 1) != 0
|
||||
|| s_sensor->set_ae_level(s_sensor, level) != 0) return ESP_FAIL;
|
||||
ESP_LOGI(TAG, "OV5640 auto exposure bias: requested %d EV, driver level %d, target %d / 55",
|
||||
ev, level, (level + 5) * 10 + 5);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
int cam_wrapper_exposure_lines(void)
|
||||
{
|
||||
if (!s_sensor || s_sensor->id.PID != OV5640_PID) return -1;
|
||||
int raw = s_sensor->get_reg(s_sensor, 0x3500, 0xfffff);
|
||||
return raw < 0 ? -1 : raw >> 4;
|
||||
}
|
||||
|
||||
int cam_wrapper_gain_raw(void)
|
||||
{
|
||||
if (!s_sensor || s_sensor->id.PID != OV5640_PID) return -1;
|
||||
return s_sensor->get_reg(s_sensor, 0x350a, 0x03ff);
|
||||
}
|
||||
|
||||
esp_err_t cam_wrapper_set_shutter_lines(unsigned lines)
|
||||
{
|
||||
if (!s_sensor || s_sensor->id.PID != OV5640_PID)
|
||||
return lines == 0 ? ESP_OK : ESP_ERR_NOT_SUPPORTED;
|
||||
int vts = s_sensor->get_reg(s_sensor, 0x380e, 0xffff);
|
||||
if (vts <= 0 || lines > (unsigned)vts) return ESP_ERR_INVALID_ARG;
|
||||
int previous = cam_wrapper_exposure_lines();
|
||||
if (s_sensor->set_exposure_ctrl(s_sensor, lines == 0) != 0
|
||||
|| s_sensor->set_gain_ctrl(s_sensor, 1) != 0) return ESP_FAIL;
|
||||
if (lines && (s_sensor->set_aec_value(s_sensor, (int)lines) != 0
|
||||
|| cam_wrapper_exposure_lines() != (int)lines)) return ESP_FAIL;
|
||||
ESP_LOGI(TAG, "OV5640 shutter: previous %d lines, requested %u lines (0=auto), AGC enabled",
|
||||
previous, lines);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t cam_wrapper_set_horizontal_timing(unsigned hts)
|
||||
{
|
||||
if (!hts) return ESP_OK;
|
||||
if (!s_sensor || s_sensor->id.PID != OV5640_PID) return ESP_ERR_NOT_SUPPORTED;
|
||||
int start = s_sensor->get_reg(s_sensor, 0x3800, 0x0fff);
|
||||
int end = s_sensor->get_reg(s_sensor, 0x3804, 0x0fff);
|
||||
int previous = s_sensor->get_reg(s_sensor, 0x380c, 0x0fff);
|
||||
if (start < 0 || end < start || previous <= 0) return ESP_FAIL;
|
||||
unsigned active = (unsigned)(end - start + 1) / (s_sensor->status.binning ? 2 : 1);
|
||||
/* Preserve the active readout window and a conservative blanking margin.
|
||||
* This is a guard against invalid geometry, not a stability guarantee. */
|
||||
if ((hts & 1) || hts < active + 64 || hts > 4095) return ESP_ERR_INVALID_ARG;
|
||||
/* Use the byte order that cannot transiently shorten the line below
|
||||
* either valid endpoint while updating this live sensor. */
|
||||
int rc;
|
||||
if (hts < (unsigned)previous) {
|
||||
rc = s_sensor->set_reg(s_sensor, 0x380d, 0xff, hts & 0xff)
|
||||
|| s_sensor->set_reg(s_sensor, 0x380c, 0x0f, hts >> 8);
|
||||
} else {
|
||||
rc = s_sensor->set_reg(s_sensor, 0x380c, 0x0f, hts >> 8)
|
||||
|| s_sensor->set_reg(s_sensor, 0x380d, 0xff, hts & 0xff);
|
||||
}
|
||||
if (rc || s_sensor->get_reg(s_sensor, 0x380c, 0x0fff) != (int)hts) return ESP_FAIL;
|
||||
ESP_LOGI(TAG, "OV5640 horizontal timing: HTS %d -> %u, active %u, VTS %d (PLL/XCLK unchanged)",
|
||||
previous, hts, active, s_sensor->get_reg(s_sensor, 0x380e, 0xffff));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t cam_wrapper_set_exposure_limit(unsigned lines)
|
||||
{
|
||||
if (!lines) return ESP_OK;
|
||||
if (!s_sensor || s_sensor->id.PID != OV5640_PID) return ESP_ERR_NOT_SUPPORTED;
|
||||
int vts = s_sensor->get_reg(s_sensor, 0x380e, 0xffff);
|
||||
int band50 = s_sensor->get_reg(s_sensor, 0x3a08, 0x03ff);
|
||||
int band60 = s_sensor->get_reg(s_sensor, 0x3a0a, 0x03ff);
|
||||
if (vts <= 4 || lines > (unsigned)(vts - 4) || band50 <= 0 || band60 <= 0)
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
unsigned max50 = lines / (unsigned)band50;
|
||||
unsigned max60 = lines / (unsigned)band60;
|
||||
if (!max50) max50 = 1;
|
||||
if (!max60) max60 = 1;
|
||||
if (max50 > 63 || max60 > 63) return ESP_ERR_INVALID_ARG;
|
||||
int rc = s_sensor->set_exposure_ctrl(s_sensor, 0)
|
||||
|| s_sensor->set_aec2(s_sensor, 0)
|
||||
|| s_sensor->set_reg(s_sensor, 0x3a02, 0xff, lines >> 8)
|
||||
|| s_sensor->set_reg(s_sensor, 0x3a03, 0xff, lines & 0xff)
|
||||
|| s_sensor->set_reg(s_sensor, 0x3a14, 0x0f, lines >> 8)
|
||||
|| s_sensor->set_reg(s_sensor, 0x3a15, 0xff, lines & 0xff)
|
||||
|| s_sensor->set_reg(s_sensor, 0x3a0d, 0x3f, max60)
|
||||
|| s_sensor->set_reg(s_sensor, 0x3a0e, 0x3f, max50)
|
||||
|| s_sensor->set_gain_ctrl(s_sensor, 1)
|
||||
|| s_sensor->set_exposure_ctrl(s_sensor, 1);
|
||||
if (rc || s_sensor->get_reg(s_sensor, 0x3a02, 0xffff) != (int)lines
|
||||
|| s_sensor->get_reg(s_sensor, 0x3a14, 0x0fff) != (int)lines)
|
||||
return ESP_FAIL;
|
||||
ESP_LOGI(TAG, "OV5640 auto exposure cap: %u lines, band maxima 50Hz=%u / 60Hz=%u, AEC/AGC auto, night mode off",
|
||||
lines, max50, max60);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
float cam_wrapper_probe_frame_rate(void)
|
||||
{
|
||||
/* Drain the old three-buffer pipeline after changing the colour effect. */
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
cam_wrapper_fb_t fb;
|
||||
if (cam_wrapper_fb_get(&fb, CAM_WRAPPER_SENSOR_AUTO) == ESP_OK)
|
||||
cam_wrapper_fb_release(&fb);
|
||||
}
|
||||
return cam_wrapper_measure_fps(CAM_WRAPPER_SENSOR_AUTO);
|
||||
}
|
||||
|
||||
esp_err_t cam_wrapper_temperature_init(void)
|
||||
{
|
||||
if (s_temperature) return ESP_OK;
|
||||
temperature_sensor_config_t config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(20, 100);
|
||||
esp_err_t rc = temperature_sensor_install(&config, &s_temperature);
|
||||
if (rc != ESP_OK) return rc;
|
||||
rc = temperature_sensor_enable(s_temperature);
|
||||
if (rc != ESP_OK) {
|
||||
temperature_sensor_uninstall(s_temperature);
|
||||
s_temperature = NULL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
esp_err_t cam_wrapper_temperature_read(float *celsius)
|
||||
{
|
||||
if (!s_temperature) return ESP_ERR_INVALID_STATE;
|
||||
return temperature_sensor_get_celsius(s_temperature, celsius);
|
||||
}
|
||||
|
||||
esp_err_t cam_wrapper_fb_get(cam_wrapper_fb_t *out, cam_wrapper_sensor_t sensor)
|
||||
@@ -190,10 +345,29 @@ esp_err_t cam_wrapper_fb_get(cam_wrapper_fb_t *out, cam_wrapper_sensor_t sensor)
|
||||
out->height = fb->height;
|
||||
out->format = (int)fb->format;
|
||||
out->handle = fb;
|
||||
out->capture_started_us = (int64_t)fb->timestamp.tv_sec * 1000000 + fb->timestamp.tv_usec;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t cam_wrapper_fb_get_latest(cam_wrapper_fb_t *out, uint32_t timeout_ms)
|
||||
{
|
||||
if (!out) return ESP_ERR_INVALID_ARG;
|
||||
memset(out, 0, sizeof(*out));
|
||||
uint32_t skipped = 0;
|
||||
camera_fb_t *fb = esp_camera_fb_get_latest(timeout_ms, &skipped);
|
||||
if (!fb) return ESP_FAIL;
|
||||
out->data = fb->buf;
|
||||
out->len = fb->len;
|
||||
out->width = fb->width;
|
||||
out->height = fb->height;
|
||||
out->format = (int)fb->format;
|
||||
out->handle = fb;
|
||||
out->capture_started_us = (int64_t)fb->timestamp.tv_sec * 1000000 + fb->timestamp.tv_usec;
|
||||
out->skipped_before = skipped;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void cam_wrapper_fb_release(cam_wrapper_fb_t *fb)
|
||||
{
|
||||
if (fb == NULL || fb->handle == NULL) {
|
||||
|
||||
@@ -112,6 +112,8 @@ typedef struct {
|
||||
size_t height;
|
||||
int format;
|
||||
void *handle; /* internal driver frame; do not touch */
|
||||
int64_t capture_started_us; /* same monotonic clock as esp_timer_get_time */
|
||||
uint32_t skipped_before; /* older completed frames released by latest get */
|
||||
} cam_wrapper_fb_t;
|
||||
|
||||
esp_err_t cam_wrapper_init(const cam_wrapper_config_t *config, cam_wrapper_sensor_t sensor);
|
||||
@@ -119,6 +121,7 @@ esp_err_t cam_wrapper_init(const cam_wrapper_config_t *config, cam_wrapper_senso
|
||||
/* Acquire one frame into `out`. Returns ESP_OK on success, ESP_FAIL if the
|
||||
* driver could not hand out a frame (e.g. timeout or buffer starvation). */
|
||||
esp_err_t cam_wrapper_fb_get(cam_wrapper_fb_t *out, cam_wrapper_sensor_t sensor);
|
||||
esp_err_t cam_wrapper_fb_get_latest(cam_wrapper_fb_t *out, uint32_t timeout_ms);
|
||||
|
||||
void cam_wrapper_fb_release(cam_wrapper_fb_t *fb);
|
||||
|
||||
@@ -129,6 +132,26 @@ esp_err_t cam_wrapper_deinit(void);
|
||||
/* Name of the sensor the driver detected, or "unknown". Never NULL. */
|
||||
const char *cam_wrapper_sensor_name(void);
|
||||
|
||||
/* Keep the sensor's JPEG encoder; effect 2 removes chroma in the sensor ISP. */
|
||||
esp_err_t cam_wrapper_set_grayscale(int enabled);
|
||||
/* Runtime JPEG compression, 0..63, without restarting the camera pipeline. */
|
||||
esp_err_t cam_wrapper_set_quality(unsigned quality);
|
||||
/* OV5640 target bias: 0 = default, -1 / -2 select approximately -1.1 / -1.9 EV. */
|
||||
esp_err_t cam_wrapper_set_exposure_bias(int ev);
|
||||
/* 0 = auto shutter; nonzero holds that exposure in sensor lines, AGC stays on. */
|
||||
esp_err_t cam_wrapper_set_shutter_lines(unsigned lines);
|
||||
/* Keep AEC/AGC automatic, cap exposure lines, and disable night frame extension. */
|
||||
esp_err_t cam_wrapper_set_exposure_limit(unsigned lines);
|
||||
/* 0 keeps the driver timing; nonzero changes OV5640 horizontal total only. */
|
||||
esp_err_t cam_wrapper_set_horizontal_timing(unsigned hts);
|
||||
int cam_wrapper_exposure_lines(void);
|
||||
/* OV5640 gain in 1/16 units; -1 when unavailable. */
|
||||
int cam_wrapper_gain_raw(void);
|
||||
float cam_wrapper_probe_frame_rate(void);
|
||||
/* Silicon temperature, not ambient or camera-sensor temperature. */
|
||||
esp_err_t cam_wrapper_temperature_init(void);
|
||||
esp_err_t cam_wrapper_temperature_read(float *celsius);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -733,13 +733,14 @@ camera_fb_t *cam_take(TickType_t timeout)
|
||||
for (;;)
|
||||
{
|
||||
TickType_t elapsed = xTaskGetTickCount() - start; /* TickType_t is unsigned so rollover is safe */
|
||||
if (elapsed >= timeout) {
|
||||
if (timeout && elapsed >= timeout) {
|
||||
ESP_LOGW(TAG, "Failed to get frame: timeout");
|
||||
return NULL;
|
||||
}
|
||||
TickType_t remaining = timeout - elapsed;
|
||||
TickType_t remaining = timeout ? timeout - elapsed : 0;
|
||||
|
||||
if (xQueueReceive(cam_obj->frame_buffer_queue, (void *)&dma_buffer, remaining) == pdFALSE) {
|
||||
if (!timeout) return NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -750,6 +751,7 @@ camera_fb_t *cam_take(TickType_t timeout)
|
||||
if (dma_reset_counter < MAX_GDMA_RESETS) {
|
||||
ll_cam_dma_reset(cam_obj);
|
||||
dma_reset_counter++;
|
||||
if (!timeout) return NULL;
|
||||
continue; /* retry with queue timeout */
|
||||
}
|
||||
if (dma_reset_counter == MAX_GDMA_RESETS) {
|
||||
@@ -762,6 +764,7 @@ camera_fb_t *cam_take(TickType_t timeout)
|
||||
CAM_WARN_THROTTLE(warn_null_cnt,
|
||||
"Unexpected NULL frame on " CONFIG_IDF_TARGET);
|
||||
#endif
|
||||
if (!timeout) return NULL;
|
||||
vTaskDelay(1); /* immediate yield once resets are done */
|
||||
continue; /* go to top of loop */
|
||||
}
|
||||
@@ -802,6 +805,7 @@ skip_eoi_check:
|
||||
CAM_WARN_THROTTLE(warn_eoi_miss_cnt,
|
||||
"NO-EOI - JPEG end marker missing");
|
||||
cam_give(dma_buffer);
|
||||
if (!timeout) return NULL;
|
||||
continue; /* wait for another frame */
|
||||
} else if (cam_obj->psram_mode &&
|
||||
cam_obj->in_bytes_per_pixel != cam_obj->fb_bytes_per_pixel) {
|
||||
@@ -818,6 +822,24 @@ skip_eoi_check:
|
||||
}
|
||||
}
|
||||
|
||||
camera_fb_t *cam_take_latest(TickType_t timeout, uint32_t *skipped)
|
||||
{
|
||||
if (skipped) *skipped = 0;
|
||||
camera_fb_t *latest = cam_take(timeout);
|
||||
if (!latest) return NULL;
|
||||
/* A bounded drain, not availability-check + blocking get: the producer can
|
||||
* replace queued buffers on the other core. Retain the valid frame if a
|
||||
* newer candidate is invalid or a GDMA-reset sentinel is encountered. */
|
||||
for (size_t i = 1; i < cam_obj->frame_cnt; i++) {
|
||||
camera_fb_t *next = cam_take(0);
|
||||
if (!next) break;
|
||||
cam_give(latest);
|
||||
latest = next;
|
||||
if (skipped) (*skipped)++;
|
||||
}
|
||||
return latest;
|
||||
}
|
||||
|
||||
void cam_give(camera_fb_t *dma_buffer)
|
||||
{
|
||||
for (int x = 0; x < cam_obj->frame_cnt; x++) {
|
||||
|
||||
@@ -401,6 +401,19 @@ camera_fb_t *esp_camera_fb_get()
|
||||
return fb;
|
||||
}
|
||||
|
||||
camera_fb_t *esp_camera_fb_get_latest(uint32_t timeout_ms, uint32_t *skipped)
|
||||
{
|
||||
if (skipped) *skipped = 0;
|
||||
if (s_state == NULL) return NULL;
|
||||
camera_fb_t *fb = cam_take_latest(pdMS_TO_TICKS(timeout_ms), skipped);
|
||||
if (fb) {
|
||||
fb->width = resolution[s_state->sensor.status.framesize].width;
|
||||
fb->height = resolution[s_state->sensor.status.framesize].height;
|
||||
fb->format = s_state->sensor.pixformat;
|
||||
}
|
||||
return fb;
|
||||
}
|
||||
|
||||
void esp_camera_fb_return(camera_fb_t *fb)
|
||||
{
|
||||
if (s_state == NULL) {
|
||||
|
||||
@@ -206,6 +206,9 @@ esp_err_t esp_camera_deinit(void);
|
||||
* @return pointer to the frame buffer
|
||||
*/
|
||||
camera_fb_t* esp_camera_fb_get(void);
|
||||
/* At most timeout_ms waiting for the first frame; pending frames are drained
|
||||
* nonblocking, keeping the newest valid JPEG. skipped may be NULL. */
|
||||
camera_fb_t* esp_camera_fb_get_latest(uint32_t timeout_ms, uint32_t *skipped);
|
||||
|
||||
/**
|
||||
* @brief Return the frame buffer to be reused again.
|
||||
|
||||
@@ -52,6 +52,8 @@ void cam_stop(void);
|
||||
void cam_start(void);
|
||||
|
||||
camera_fb_t *cam_take(TickType_t timeout);
|
||||
/* Hold only the newest validated frame, returning superseded buffers. */
|
||||
camera_fb_t *cam_take_latest(TickType_t timeout, uint32_t *skipped);
|
||||
|
||||
void cam_give(camera_fb_t *dma_buffer);
|
||||
|
||||
|
||||
@@ -15,3 +15,6 @@ dist/
|
||||
# 截图不是源码,它们随界面一起变;要看就去 `pnpm app` 跑起来,或者自己跑 .dev/shoot.js。
|
||||
/.dev/
|
||||
/screenshots/
|
||||
|
||||
# 测试运行时被 XDG_CONFIG_HOME 相对路径写出来的配置,不是仓库内容。
|
||||
src-tauri/.config/
|
||||
|
||||
+9
-474
@@ -1,482 +1,17 @@
|
||||
# desktop
|
||||
# RMIC::2026 桌面端
|
||||
|
||||
桌面端。**两种身份,两套页面,一个进程。**
|
||||
Tauri v2 + React + Rust 客户端。用户端显示本队图传、双方装甲条、瞄准器与命中反馈;裁判端显示双路画面、装甲板列表,并支持队名编辑、槽位分配、截图与录制回放。
|
||||
|
||||
| 身份 | 画面 | 血量 | 装甲板 | 绑定 |
|
||||
|---|---|---|---|---|
|
||||
| 用户端(`team`) | 一路 | **双方**(左上角自己 / 右上角对手) | 自己那块 | **要,改动需密码** |
|
||||
| 裁判端(`referee`) | 两路并排 | 双方(各自贴画面角上,**队名可改**) | 发现列表 + 槽位 | 不需要 |
|
||||
|
||||
界面是 WebView 里的 React(画面画在 canvas 上),后端是 Rust。**它不是把旧客户端套进
|
||||
一个窗口**:网络、重组、发现、配置、密码、录制全部重写成了可单测的模块。
|
||||
|
||||
```text
|
||||
desktop/
|
||||
core/ cam-core:不含任何界面与 Tauri 的东西(80 个测试)
|
||||
src-tauri/ Tauri 后端:推流、命令、录制(30 个测试)
|
||||
src/ React 前端:两套页面 + HUD
|
||||
src/lib/ IPC 封装、WebSocket 客户端、状态存储、格式化
|
||||
src/components/ 视频窗格、血条、胜负横幅、瞄准器、装甲板表、队名编辑、录制面板、设置抽屉
|
||||
src/pages/ 用户端 / 裁判端
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
在本目录执行:
|
||||
|
||||
```sh
|
||||
cd desktop
|
||||
pnpm install
|
||||
pnpm app # = tauri dev:起 vite + 编 Rust + 开窗口
|
||||
pnpm build # 只构建前端(tsc --noEmit && vite build)
|
||||
pnpm tauri build # 出 .app 与 .dmg
|
||||
pnpm app
|
||||
```
|
||||
|
||||
下面两条是 Rust 侧,用它验证不依赖窗口的部分:
|
||||
`pnpm build` 构建前端,`pnpm tauri build` 打包应用。默认 Rust target 是 macOS ARM;Windows 和 Linux 需显式指定宿主 target。
|
||||
|
||||
```sh
|
||||
cd desktop/core && cargo test # 80
|
||||
cd desktop/src-tauri && cargo test # 30,其中几条真的会起 ffmpeg
|
||||
```
|
||||
|
||||
### 演示模式:没有板子也能把整条链路跑起来
|
||||
|
||||
```sh
|
||||
cd desktop
|
||||
CAM_DEMO=1 pnpm app
|
||||
```
|
||||
|
||||
它**不伪造数据给界面**,而是合成**协议数据报**,从 `Receiver::datagram` 与
|
||||
`ArmorHealthStore::datagram` 进去。也就是说演示模式跑的是同一条真实路径:
|
||||
|
||||
```text
|
||||
合成 JPEG ──分片──▶ Receiver::datagram ──重组──▶ 帧 sink ──▶ WebSocket ──▶ canvas
|
||||
合成血量 ─────────▶ ArmorHealthStore ──▶ HealthTracker ──▶ 命中反馈
|
||||
```
|
||||
|
||||
分片、重组、发现、统计、掉血派生、两道闸门全在跑真代码,截图与录制也不需要特殊分支。
|
||||
界面上会有一条 **演示模式** 标记,板子 id 也刻意取成 `0x0DE00001` 这种一眼能看出是人造
|
||||
的形状——**别把它当成现场数据**。
|
||||
|
||||
### 命令行与环境变量
|
||||
|
||||
```text
|
||||
--role <team|referee> 身份。默认看配置文件
|
||||
--config <路径> 配置文件位置
|
||||
--demo 演示模式
|
||||
--list 打印发现的摄像头与装甲板后退出
|
||||
-h, --help 帮助
|
||||
|
||||
CAM_DEMO=1 等价于 --demo
|
||||
CAM_CONFIG=<路径> 等价于 --config <路径>
|
||||
```
|
||||
|
||||
两个环境变量存在的理由很实际:`tauri dev` 要用两个 `--` 才能把参数递给应用
|
||||
(`tauri dev -- -- --demo`),写在一行环境变量里不会有人记错。
|
||||
|
||||
**未知参数只记一条 debug 日志,不报错。** macOS 从访达双击 .app 时系统会塞进来一个
|
||||
`-psn_0_123456`;把它当错误处理的表现是"双击图标什么都没发生"。
|
||||
|
||||
## 为什么换成 Tauri,代价是什么
|
||||
|
||||
旧客户端是 Rust + Slint 单二进制,能用。换掉它的理由是**界面这一侧的生产力**:血条、
|
||||
瞄准器、可拖拽的偏移校准、下拉绑定、设置抽屉、录制列表与回放——这些用 CSS/HTML 写是
|
||||
几十分钟的事,用即时模式 UI 工具写要自己排版、自己画控件、自己处理焦点。这个项目的重心
|
||||
在链路(UDP、重组、发现、防作弊),界面应当越快改完越好。
|
||||
|
||||
代价说清楚:
|
||||
|
||||
- **多一层**。WebView + Node 工具链 + Tauri 的权限系统,比一个 Slint 二进制重得多。
|
||||
- **要联网装依赖**(前端 npm、Rust crates)。
|
||||
- **画面要过一条本地通道**才能到界面(见下一节)。Slint 那版是把 RGBA 直接交给渲染器,
|
||||
路径更短。
|
||||
|
||||
换来的另一样东西是**可测性**:`core/` 不认识界面,所以重组、发现、帧率窗口、槽位分配、
|
||||
配置与密码全都能用单元测试盯住——而它们恰好是最容易错、错了又最难在现场看出来的部分。
|
||||
|
||||
## 视频怎么进 WebView
|
||||
|
||||
**一个只监听回环地址的 WebSocket,推二进制 JPEG。** 界面从 `stream_info` 命令拿到
|
||||
`ws://127.0.0.1:<随机端口>/?token=...`。
|
||||
|
||||
### 为什么不是 Tauri 的 IPC
|
||||
|
||||
两路 22fps、每帧约 22 kB,每秒约 1 MB 的裸 JPEG。走 IPC 要序列化成 JSON(base64 再涨
|
||||
1/3),前端再解回来——CPU 花在没人需要的编解码上,延迟白涨一截。二进制 WebSocket 帧把
|
||||
原始字节直接交给前端,浏览器用**原生解码器**解,**后端连 JPEG 都不用解**。
|
||||
|
||||
### 二进制帧的布局
|
||||
|
||||
```text
|
||||
0 4 8 10 12 16
|
||||
+--------------+--------------+------+------+--------------+---------+
|
||||
| sender_id u32| frame_id u32 | w u16| h u16| jpeg_len u32 | JPEG... |
|
||||
+--------------+--------------+------+------+--------------+---------+
|
||||
```
|
||||
|
||||
小端,16 字节头。前后端对这段布局的约定由一个测试盯着(`server.rs` 与 `stream.ts`
|
||||
两侧都有),长度不一致的帧宁可丢掉,也不要让前端拿到一段不完整的 JPEG。
|
||||
|
||||
### 前端侧的两个陷阱
|
||||
|
||||
**解码是异步的,所以帧号要看着用。** `createImageBitmap` 返回 Promise,两个相邻帧的解码
|
||||
可能乱序完成。所以每路记住最后画过的帧号,只有更新的那帧才采用——没有这一步,画面会偶尔
|
||||
往回跳一帧,看起来像"卡了一下",极难归因。
|
||||
|
||||
**`ImageBitmap` 必须显式 `close()`。** 它持有解码后的像素,不吃 GC 的账。每收到新帧就
|
||||
替换旧的那张并不够,必须把旧的那张关掉,否则两路 22fps 跑几分钟就是几百 MB。
|
||||
|
||||
## 防作弊:两道闸门,不是一个
|
||||
|
||||
对抗赛里两队都用这套系统看自己小车的图传,要防止对手开第二个窗口连到另一队的板子。
|
||||
|
||||
**关键是这件事做在接收端,不是界面里。** 广播的 Hello 里就有 MAC 派生的 `sender_id` 与
|
||||
IP,藏起来毫无意义;数据也可能已经收下并解码了,`.slint`/CSS 只控制显示。
|
||||
|
||||
| 闸门 | 管什么 | 什么时候开 |
|
||||
|---|---|---|
|
||||
| `only_sender` | 帧的重组与投递 | 用户端**始终**是绑定的那一块 |
|
||||
| `discovery_all` | 是否记录**其它**板子的 Hello | 只在密码解锁后的设置界面里打开 |
|
||||
|
||||
旧客户端把两件事绑在同一道闸门上,于是只能在"能列出候选"和"挡住对手"之间二选一,改绑
|
||||
只能靠手改配置文件。现在分开了:设置界面能列出候选摄像头(否则无从选起),但**画面不进
|
||||
来**——对手的板子即使把流推到本机端口、即使被记进发现列表,它的帧也进不了重组器。
|
||||
|
||||
四种情况不限制:裁判端(中立方)、debug 构建、用户端还没绑定、`--camera-id` 显式覆盖。
|
||||
|
||||
### 遥测按编译模式门控
|
||||
|
||||
`TEAM_DIAGNOSTICS = cfg!(debug_assertions)`。用户端 release 下**不产出**帧率/丢帧这些
|
||||
数字——不是"界面藏起来",是 DTO 里就不存在(`CameraStatsDto::build` 直接把它们置零)。
|
||||
由编译模式决定而不是命令行开关:开关意味着二进制里带着那套东西,忘开一次就泄露了。
|
||||
|
||||
### 这挡不住什么
|
||||
|
||||
诚实地说:**IP 仍然通过 Hello 广播泄露。** 对手知道你的 IP 之后可以自己发一个
|
||||
`Subscribe` 过来——固件会向任何订阅者推流,那道闸门在他的客户端上,不在你的。真要挡住
|
||||
需要固件侧做配对 token(首次绑定发一个令牌,固件只接受带令牌的订阅),代价是固件重启就
|
||||
失配。
|
||||
|
||||
回环通道上那个 token 也不是防对手的:它只让"本机另一个程序顺手连上来看画面"变成一件需要
|
||||
先读到那个 token 的事。
|
||||
|
||||
## 密码:它挡的是谁
|
||||
|
||||
必须说清楚,否则会被当成一把它并不是的锁。
|
||||
|
||||
- **挡得住**:现场随手改绑定。选手想把自己的画面换到对手那块板子上,得先知道密码;围观
|
||||
的人翻不到明文(只有 Argon2id 哈希),也不能靠改配置文件把绑定改掉——改了之后界面会
|
||||
要求先解锁。**退出/关闭程序**同样要密码,因为"随手关掉"也是一种要挡的误操作。
|
||||
- **挡不住**:一个愿意在自己机器上重编这个客户端的人。配置与二进制都在他手上,删掉哈希
|
||||
或者直接改源码都行。真要封住得回固件加配对 token。
|
||||
|
||||
所以它的定位是**防误触与防随手改**,不是密码学意义上的对抗。
|
||||
|
||||
实现上:
|
||||
|
||||
- Argon2id(`argon2` crate,默认参数 19 MiB / t=2),存标准的 PHC 字符串,参数跟着哈希
|
||||
一起躺在配置里,以后调参不需要迁移。用快哈希(SHA-256 之类)对 4 位数字密码等于没有
|
||||
保护。
|
||||
- 首次启动**强制**设置密码(`Config::needs_password_setup`),最短 4 位。长度下限只要
|
||||
4 位是刻意的:一个 8 位强密码在这里换不来任何实际安全增益,却会让现场每次改绑都要掏
|
||||
手机查一遍。
|
||||
- 解锁一次管到进程结束(会话内)。用户端可以手动"上锁"。
|
||||
- **裁判端不要密码**:它没有"绑定到自己那块板子"的动作可被滥用,而现场裁判要能最快地改
|
||||
槽位、开录。
|
||||
- **离开用户端要密码**:否则切到裁判端就能看两路画面、还能随便改配置,闸门形同虚设。
|
||||
|
||||
执行点在 Rust 侧:每个改变配置的命令都过 `AppState::require_unlocked`。界面上的解锁
|
||||
对话框只是入口——即使有人从控制台直接调 `invoke("bind_camera")`,一样要过这一关。
|
||||
|
||||
**故意没有 `--camera-id` 这类命令行覆盖。** 旧客户端有一个,用来"临时把一台机器当另一台
|
||||
用";在这里它等于一条不需要密码就能改绑的路,所以去掉。要临时换一块板子,就在设置里改
|
||||
(要密码),或者用 `--config` 指一份另外的配置文件。
|
||||
|
||||
忘了密码:删掉配置文件里的 `password` 一行(或整个文件)。旧客户端的配置会在首次启动时
|
||||
导入并备份成 `.bak`。
|
||||
|
||||
## 用户端 HUD
|
||||
|
||||
### 血条:两个上角,平行四边形,一条分 5 格
|
||||
|
||||
**画面左上角是自己,右上角是对手**——两条同时看得见。血量是这一屏上最需要对照的两个数,
|
||||
放在画面之外意味着视线要离开取景。条本身是斜的(平行四边形),里面分成 **5 格**,一格
|
||||
20 点血:格子少而大,隔着几米扫一眼也能数出"还剩几格",比读一条连续的长度或一个百分比快。
|
||||
少了的那部分从**靠中心的一侧**开始灭,于是两条血一起减少时视觉上往两边退。
|
||||
|
||||
(条挂在绝对定位的一层上,`pointer-events: none`——那一层盖着画面,而画面要能滚轮缩放、
|
||||
指针拖动。同理,倍率与复位徽标从画面**上**沿挪到了下沿,上沿两个角归血条。)
|
||||
|
||||
宽度、是否显示数字/队名、低血阈值与脉动都可配;阈值以内的格子提前染上警示色,所以"危险区
|
||||
在哪"是一直看得见的,而不是等真的低血了才第一次出现。
|
||||
|
||||
**未接入时显示占位说明而不是 0**:0 是一个看起来像真值的假值,现场看到 0 会以为对方已经
|
||||
没血了,然后开始找裁判理论。这一条在旧客户端上是踩过坑的。
|
||||
|
||||
血量归零**不写"已击毁/损毁"这类话**:归零这件事由图传中央的胜负横幅表达(见下),条上只留
|
||||
一个 0 与灭掉的格子。
|
||||
|
||||
### 胜负横幅
|
||||
|
||||
一方归零之后,**图传中央**显示结论:赢的一边是「胜利」,输的一边是「击败」,两边同时归零是
|
||||
「平局」(那时候任何一边显示"胜利"都是错的)。用词带上对手的名字,裁判端两格因此也能直接
|
||||
看出是哪一边赢了。
|
||||
|
||||
- 结论由 Rust 侧算(`snapshot::resolve_outcomes`,有单测):**输只需要看自己**——自己归零就是
|
||||
输,哪怕对手那块板子掉线;**赢要看两边**——对手归零且自己还有血,否则一块从没上报过血量的
|
||||
自家主板会让人"凭空胜利"。
|
||||
- 它一直显示到血量被重置(长按 BOOT / 下一局开始),不做几秒淡出:淡出之后屏幕上就再没有
|
||||
"这局结束了"的痕迹,截图、回放与现场的争议都无从对照。
|
||||
- 这是唯一放在画面正中的东西。命中反馈那一套刻意避开正中(见下),因为**胜负不是那种信息**:
|
||||
它是一局的结束,此刻不再需要瞄准。
|
||||
|
||||
### 瞄准器
|
||||
|
||||
画在 canvas 上(不是叠一层 DOM),因为**它必须跟着画面走**:它标的是相机光轴上的一个点。
|
||||
用 DOM 摆一个 `<img>` 再叠一层 `transform` 的话,"图像落在哪、瞄准点落在哪"要算两遍,而
|
||||
两份实现迟早会不一致——表现是放大之后瞄准器偏了,恰恰发生在这件事最要紧的时候。所以变换
|
||||
只算一次,图像、网格、瞄准器、伤害数字全都用同一组数画出来。
|
||||
|
||||
尺寸与缩放的关系也是刻意分开的:
|
||||
|
||||
| | 跟着缩放走吗 | 为什么 |
|
||||
|---|---|---|
|
||||
| 位置 | **是** | 它指的是画面里的一个点 |
|
||||
| 大小 | 否 | 像真瞄具一样,放大之后准星覆盖画面里更小的一块 |
|
||||
| 网格 | 是 | 它标的是画面里的比例,用来估距离 |
|
||||
|
||||
**偏移校准存的是画面比例,不是像素。** 摄像头与枪管不同轴时要把瞄点挪到弹着点上,而像素
|
||||
偏移在分辨率或窗口大小一变就全错了。校准可以拖着画面调,也可以直接填数值。
|
||||
|
||||
### 命中反馈
|
||||
|
||||
主板**不报**"被打了一下",只报"现在是多少血"。所以命中是靠相邻两次读数之差还原的
|
||||
(`HealthTracker`):
|
||||
|
||||
- 血量减少 ⇒ 一次命中,伤害是两个读数之差;
|
||||
- 血量增加 ⇒ 不是命中,是长按 BOOT 的重置;
|
||||
- 第一次见到一块板子不产生命中——没有基线,此时 `hp = 0` 只是"已经没血了"。
|
||||
|
||||
按**板子**记基线而不是按 `(板子, index)`:主板一次广播四条、`hp` 相同,按四条各记一份
|
||||
基线会让一次掉血被报成四次命中。
|
||||
|
||||
已知精度上限:两次读数之间发生两次伤害时只能看到净差。心跳 200 ms,所以这段窗口里的两次
|
||||
打击会被合并成一次——对"闪一下红"足够,对判定不够,判定看的是界面上的绝对血量。
|
||||
|
||||
反馈有**两样**:整屏闪红、准星旁的伤害数字。**大号数字不放在画面正中**——第一版放了,
|
||||
结果它正好压在瞄准点上,而被打中的那一下恰恰是最需要看着准星的时候。
|
||||
|
||||
**没有"画面抖动"了。** 原来有第三样:被打中时整幅图(连瞄准器一起)晃一下。它去掉了,
|
||||
理由有两条:
|
||||
|
||||
- 抖动恰好发生在最需要看清画面的那一下;
|
||||
- 它把**画面**动了,而操作手看到画面动时的第一反应是"车在晃",不是"我被打中了"——一个
|
||||
只有几百毫秒的信号,代价是让人怀疑一个不存在的问题。
|
||||
|
||||
`HitFeedbackConfig::shake` 这个字段留着但不再生效:配置文件是 `deny_unknown_fields` 的,
|
||||
删字段会让所有写过 `shake = true` 的配置**解析失败**,而配置解析失败等于程序起不来。设置
|
||||
面板里也没有这个开关了。
|
||||
|
||||
## 裁判端
|
||||
|
||||
### 队名可以直接改
|
||||
|
||||
两格画面各自下面有一个队名输入框(`TeamNameEditor`),回车或点到别处生效,Esc 取消。
|
||||
名字是**这一场比赛**的属性,所以它放在裁判盯着的地方,而不是设置面板深处的某一节;改完
|
||||
立刻反映到血条、装甲板表与"等待图传"的占位文字上。
|
||||
|
||||
规范化的三件事统一在 Rust 侧做(`config::sanitize_team_name`,有单测):合并空白、按**字符**
|
||||
截断到 12 个字(按字节截断会切出半个字)、留空回落到「队伍一 / 队伍二」。超过长度在输入框里
|
||||
就限制了,比提交后被悄悄截掉诚实。
|
||||
|
||||
命令是 `set_team_name(team, name)`——**一次只改一边**。界面本来可以传一整个两条的数组,但那样
|
||||
每次改名都是"读两条、改一条、写两条",而读来的那两条来自最多 200 ms 前的快照,紧接着改另一边
|
||||
就会把上一次的改动覆盖回去。**同样过密码闸门**:裁判端本来就没有密码(`Role::needs_password`
|
||||
为假),所以对现场没有影响;而给一个不设闸门的"写配置"入口,等于在闸门上开一条旁路——今天
|
||||
改的是名字,明天有人顺手把绑定也挪进来。
|
||||
|
||||
名字在快照里以 `teamNames`(两条,下标就是队伍号)推给界面,两边各自的 `TeamHealthDto.name`
|
||||
也是从这里来的:界面各处自己拼 `team === 0 ? "队伍一" : "队伍二"` 会在改名之后留下一半旧名字,
|
||||
而且不会报错。
|
||||
|
||||
### 两格等宽
|
||||
|
||||
`flex: 1 1 0` + `min-width: 0`,两格的宽度只由容器决定。
|
||||
|
||||
旧客户端在这里翻过车:`HorizontalLayout` 先按内容的**最大**需求分配,剩下的才按 stretch
|
||||
分。一格写着"已连接,等待画面…"(8 字),另一格只有"未收到画面"(5 字),于是左边多要
|
||||
十几个像素——实测 55% / 45%。现在占位文字绝对定位、两格需求相同。
|
||||
|
||||
### 槽位分配:修掉了一个真 bug
|
||||
|
||||
规则:
|
||||
|
||||
1. 还在线的槽位不换人;
|
||||
2. **候选里排除已经被另一个槽位占着的 id**;
|
||||
3. 掉线的槽位保留它的 id(显示"离线"),这样它回来时还在原位;
|
||||
4. 没有候选就不动——只有一块板在场时另一个槽位空着,空着比显示一份复制品诚实。
|
||||
|
||||
规则 2 是修 bug。旧实现只排除"这个槽位自己原来那个 id",于是**队伍一的板子一离线,它的
|
||||
槽位就会去拿队伍二那块,两格显示同一块板子**——看起来像图传串了,现场的排查方向会是网线、
|
||||
AP、板子,而不是二十行分配代码。现在它有一个直接对着这个场景写的测试。
|
||||
|
||||
有烧错固件的板子(`team` 字段是反的)时可以在设置里改成手动指定槽位。
|
||||
|
||||
### 装甲板发现列表
|
||||
|
||||
场上连上了几块板子、各自自报哪一队、现在多少血、多久没广播、四条上报齐不齐、分到了哪个
|
||||
槽位——"裁判端没显示血量"时第一眼看的就是这张表。它能区分"板子根本没上线"、"上线了但队
|
||||
伍号烧反了"和"只有一条上报在发"。
|
||||
|
||||
**这不需要改固件**:主板一直用 `encode_from(sender_id)` 把自己的 MAC 派生 id 打进帧头,
|
||||
只是旧的解码路径把它丢了。现在 `ArmorHealth::decode_from` 把它交出来。
|
||||
|
||||
### 截图与录制
|
||||
|
||||
- 截图存 PNG(`shots/` 下),文件名带板子 id 与毫秒时间戳。
|
||||
- 录制存 mp4,走 ffmpeg 子进程:`-f image2pipe -c:v mjpeg -framerate <fps> -i pipe:0`
|
||||
喂帧,x264 编码,`+faststart`。
|
||||
- **帧率要显式告诉 ffmpeg**。`image2pipe` 里的 JPEG 序列没有时间戳,一串帧在 ffmpeg 看来
|
||||
就是"以固定帧率播放的图片"。所以这个数只能来自实测,它会写进日志与 DTO——回放快放/慢放
|
||||
时第一个要看的就是它。
|
||||
- 喂帧经由一条长度 64 的队列与一个写盘线程。**接收线程绝不能被写盘拖慢**:那一天的表现是
|
||||
链路上本不存在的丢帧。写不进去就丢帧并计数,丢帧数会出现在界面上。
|
||||
- 文件名会查重:同一秒停一次再开一次是很自然的操作("这局重录"),而重名意味着 ffmpeg 的
|
||||
`-y` 把上一段**静默覆盖**掉。所以先收尾上一段再开新的,重名往后排 `-2`、`-3`。
|
||||
|
||||
界面内回放走 Tauri 的资源协议,只对 `tauri.conf.json` 里 `assetProtocol.scope` 白名单的
|
||||
目录生效。默认录制目录在名单里;改到别处时 `<video>` 会加载失败,界面上给出"用系统播放器
|
||||
打开"的退路——而不是让人以为录下来的文件坏了。
|
||||
|
||||
## 配置
|
||||
|
||||
`~/.config/cam-desktop/config.toml`,`--config` 可改。
|
||||
|
||||
```toml
|
||||
role = "team"
|
||||
roleChosen = true
|
||||
team = 0
|
||||
teamNames = ["队伍一", "队伍二"]
|
||||
cameraId = "0x0DE00001"
|
||||
armorId = "0x0A200001"
|
||||
mirror = false
|
||||
password = "$argon2id$v=19$m=19456,t=2,p=1$..."
|
||||
|
||||
[recording]
|
||||
dir = "/Users/you/Movies/cam-desktop"
|
||||
ffmpeg = "ffmpeg"
|
||||
crf = 20
|
||||
preset = "veryfast"
|
||||
|
||||
[hud.crosshair]
|
||||
style = "cross-dot"
|
||||
size = 0.05
|
||||
offsetX = 0.0
|
||||
```
|
||||
|
||||
几个决定:
|
||||
|
||||
- **手写解析换成 serde + toml。** 旧客户端的配置只有四个标量字段,手写换来零依赖是划算
|
||||
的;现在有 HUD 的嵌套结构、密码哈希、录制参数,对称性成本超过了收益。
|
||||
- **未知键名仍然报错**(`deny_unknown_fields`)。打错一个键名却"看起来生效了",在现场是
|
||||
要浪费时间的。
|
||||
- **字段名统一 camelCase**,磁盘、IPC、前端 TS 一个名字。TOML 里写 camelCase 不算最漂亮,
|
||||
但换来的是不需要任何一层做名字转换——而那种转换漏掉一个字段时不会报错,只会让某一项设置
|
||||
看起来"改了没生效"。
|
||||
- **原子写**:先写同目录临时文件再 `rename`。配置文件里有密码哈希,写一半崩掉留下的截断
|
||||
文件会让"解锁"永久失败。
|
||||
- **id 写成十六进制字符串**(`"0x3D4D8A9A"`),与界面、日志、抓包里看到的完全一致。
|
||||
- `teamNames` 在根上而不是 `[referee]` 里:用户端的血条同样要显示名字,而"这场比赛的两队
|
||||
叫什么"是比赛本身的属性,不是裁判端这个身份的属性。
|
||||
- `roleChosen` 单独一位:`role` 的默认值是裁判端,所以"role == referee"既可能是人选了裁判
|
||||
端,也可能只是还没人碰过这个文件。界面需要这个判据来决定首启要不要先问一句。
|
||||
|
||||
旧客户端(Slint 版)的配置在首次启动时导入能对应的字段,然后改名成 `.bak`(不删:现场
|
||||
出问题时"旧的那份到底写了什么"是第一个要看的东西)。只有用**默认**路径时才做这件事——
|
||||
`--config` 指定了别的路径说明这次运行是有意分开的,那时去动常规配置是越界的。
|
||||
|
||||
## 浏览器预览(仅开发)
|
||||
|
||||
Tauri 的界面跑在 WKWebView 里,改一行 CSS 要走一次 cargo 构建。所以 dev 模式下、且页面
|
||||
里没有 Tauri 注入时,同一套 React 组件可以在浏览器里跑起来:状态与画面由
|
||||
`src/lib/dev-preview.ts` 合成。
|
||||
|
||||
```sh
|
||||
pnpm dev # 用户端
|
||||
open 'http://127.0.0.1:1420/?preview=referee' # 裁判端(密码 1234)
|
||||
```
|
||||
|
||||
两重门保证它进不了正式构建:`import.meta.env.DEV` 为真**且**没有 `__TAURI_INTERNALS__`,
|
||||
并且调用点是 `await import(...)`——条件为假时整个模块不会被打进产物。
|
||||
|
||||
## 测试
|
||||
|
||||
```sh
|
||||
cd desktop/core && cargo test # 80:重组、发现、两道闸门、帧率窗口、槽位、配置、密码、胜负判定
|
||||
cd desktop/src-tauri && cargo test # 30:帧头布局、录制、截图、演示画面、命令行
|
||||
cd protocol && cargo test # 15
|
||||
```
|
||||
|
||||
三条值得单独说的:
|
||||
|
||||
- **录制**那条真的起 ffmpeg、喂 20 帧、停,然后用 `ffprobe` 读回帧数与分辨率。只断言"文件
|
||||
存在且非空"是不够的:ffmpeg 参数写错时也会留下一个空的容器,而那正是最难在现场发现的
|
||||
一种失败。
|
||||
- **截图**那条真写一个 PNG 再读回来对尺寸,另外验证坏输入会报错而不是留下一个空文件。
|
||||
- **帧率窗口**那条把"对帧间隔取平均会给出什么"也钉住了(约 30fps),防止有人"顺手修一下"
|
||||
改回错误算法。
|
||||
|
||||
CSP 的验证不靠"看一眼没报错",而是靠**前端能不能连上后端**:前端的第一步是先 `invoke`
|
||||
拿通道地址,再连 WebSocket。所以快照里的 `clients` 从 0 变成 1 同时证明了四件事——JS 加载
|
||||
成功、IPC 通、WebSocket 通、React 的 effect 跑到了。CSP 一旦挡住其中任何一环,它就会停在
|
||||
0。手动核对的办法:
|
||||
|
||||
```sh
|
||||
RUST_LOG=debug ./target/aarch64-apple-darwin/debug/cam-desktop | grep 'stream:'
|
||||
# 拿那条 ws://... 地址,用任何 WebSocket 客户端连上去,看 clients 是不是 1
|
||||
```
|
||||
|
||||
## 线程与数据流
|
||||
|
||||
| 线程 | 做什么 | 为什么单独 |
|
||||
|---|---|---|
|
||||
| `udp-reader` | 收包、解析、按发送方重组 | 不能让解码/写盘挤进数据报之间 |
|
||||
| `armor-reader` | 收 5601 上的血量广播 | 与视频是两个端口、两种节奏 |
|
||||
| `rec-writer-<id>` | 把队列里的帧写进 ffmpeg 的 stdin | 接收线程绝不能被写盘拖慢 |
|
||||
| `demo-*`(仅演示) | 合成协议数据报 | 走同一条真实路径 |
|
||||
| tokio 任务 | WebSocket 服务、5 Hz 快照 | 异步 IO |
|
||||
|
||||
跨线程只有两样东西:`Arc` 与广播通道。帧在接收线程里编码一次(16 字节头 + JPEG),
|
||||
所有界面共用同一份 `Bytes`。
|
||||
|
||||
## 已知限制与没做的
|
||||
|
||||
- **同一队有两块装甲板在广播时**,`health(team)` 是"最后写入者优先",四个格子可能混着两块
|
||||
板子的数。真实比赛一队只有一块,所以这只在开发(演示 + 真板子)或烧错固件时才会遇到;
|
||||
日志里会警告一次,消歧办法是绑定具体的板子 id(`health_of_board`)。
|
||||
- **录制帧率**是固定值,不是可变帧率。源帧率变化大时回放速度会有偏差;这个数会写进日志与
|
||||
DTO,所以至少能解释清楚。
|
||||
- **`csp` 是一条收紧过的白名单**,但它是"够用"而不是"最小":`style-src` 带
|
||||
`'unsafe-inline'`(React 的内联 `style` 属性需要它),`connect-src` 放开了整个
|
||||
`ws://127.0.0.1:*`(端口是启动时随机分配的,写不死)。验证方式是"前端能不能连上后端"
|
||||
——见下面"测试"一节最后一条。
|
||||
- **证书与公证**没做,`.dmg` 只在本机可用。
|
||||
- 前端没有自动化测试。界面这一侧的验证手段是浏览器预览 + 真机运行;逻辑都在 `core/` 里
|
||||
有测试。
|
||||
|
||||
## 排查
|
||||
|
||||
| 现象 | 原因 |
|
||||
|---|---|
|
||||
| 起不来,提示绑不上 UDP 5600 | 同一端口上已经跑着另一个客户端(旧的 Slint 版或另一份本程序)。看 `~/.config/cam-desktop/startup-error.txt` |
|
||||
| 窗口起来了但没画面 | 设置里看"摄像头"下拉:绑定的是哪一块、在不在线 |
|
||||
| 状态栏一直"已连接,等待画面…" | 板子在线但没推流。检查它有没有在向这个源端口订阅(客户端每 800ms 续订一次) |
|
||||
| 队伍端下拉里只有一块板子 | 正常:帧的闸门是绑定的那一块。设置界面解锁后会列出全部候选 |
|
||||
| 右上角那条血显示"装甲板未接入" | 对手那块主板没在 5601 上报血量(或不在线)。显示的是说明而不是 0——0 会被当成"对手已经没血" |
|
||||
| 改了队名,某一处还是旧名字 | 不该发生:名字只有一个来源(快照里的 `teamNames`,血条直接用 `name`)。看到的话说明有一处自己拼了"队伍一/队伍二",那是个 bug |
|
||||
| 输了/赢了之后画面中央一直有字 | 刻意如此:它显示到血量被重置(长按 BOOT)为止,不做几秒淡出。想清掉就重置装甲板 |
|
||||
| 被打中时画面不动,只有闪红 | 刻意如此:抖动去掉了(见"命中反馈")。旧配置里的 `shake = true` 读得进来但不生效 |
|
||||
| 画面不动 | 先看顶栏的"界面 N":0 表示前端没接上,≥1 表示后端在推而前端没画。`RUST_LOG=debug` 会打印完整通道地址,可以用 `wscat` 连上去看 |
|
||||
| 回放失败 | 录制目录不在资源协议白名单里。用"用系统播放器打开" |
|
||||
| 录不了 | ffmpeg 不在 PATH 里。设置里可以指定路径;不起 ffmpeg 时会报错而不是假装在录 |
|
||||
| 两块画面串了 | 检查是不是手动指定了槽位,或者两块板子烧了同一个 `ARMOR_TEAM` |
|
||||
| macOS 上 WebKit 报 `could not create directory .../WebKit/...` | 运行环境禁止写 `~/Library/WebKit`(沙箱)。WebView 会退回无缓存的临时目录,功能不受影响 |
|
||||
| `libc` 找不到 `MAP_FAILED` | `desktop/.cargo/config.toml` 的 target 没改成宿主三元组 |
|
||||
- [客户端说明](../docs/desktop/client.md):构建、配置、界面行为、网络通道、权限与排查。
|
||||
- [固件维护](../docs/maintenance/firmware.md):`.zip` 固件包、坏块规避、备份与回读校验。
|
||||
- [版本维护](../docs/development/versioning.md):版本计算与派生文件。
|
||||
- [文档首页](../docs/index.md):仓库的其他说明。
|
||||
|
||||
Generated
+26
@@ -55,6 +55,7 @@ dependencies = [
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"socket2",
|
||||
"toml",
|
||||
]
|
||||
|
||||
@@ -297,6 +298,16 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
@@ -391,6 +402,21 @@ dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.15"
|
||||
|
||||
@@ -10,6 +10,7 @@ protocol = { path = "../../protocol" }
|
||||
|
||||
anyhow = "1"
|
||||
log = "0.4"
|
||||
socket2 = "0.6"
|
||||
|
||||
# 配置的序列化。这里与旧客户端(client/)的做法相反:那一版的配置只有十来个标量
|
||||
# 字段,手写 TOML 换来零依赖是划算的。现在配置里有 HUD 的嵌套结构和密码哈希,手写
|
||||
|
||||
@@ -619,9 +619,13 @@ const HEADER: &str = "\
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn home_dir() -> PathBuf {
|
||||
// Windows 的 Tauri/服务进程里通常没有 `HOME`,只有 `USERPROFILE`。
|
||||
// 只看 `HOME` 会落到 `.`,配置文件就变成相对于启动目录——安装版从开始菜单启动时
|
||||
// 启动目录是 Program Files,配置写不进去,卸载后也留不下任何东西。
|
||||
std::env::var_os("HOME")
|
||||
.filter(|p| !p.is_empty())
|
||||
.or_else(|| std::env::var_os("USERPROFILE").filter(|p| !p.is_empty()))
|
||||
.map(PathBuf::from)
|
||||
.filter(|p| !p.as_os_str().is_empty())
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
}
|
||||
|
||||
|
||||
+207
-26
@@ -11,7 +11,8 @@
|
||||
//! 两个方向各司其职:
|
||||
//!
|
||||
//! - 板子每 2 秒广播一次 [`protocol::MsgType::Hello`],客户端被动收到就知道有谁在线。
|
||||
//! - 客户端广播 [`protocol::MsgType::Subscribe`],板子收到后开始定向推流。
|
||||
//! - 客户端广播 [`protocol::MsgType::Subscribe`],并向已发现的板子单播续订。
|
||||
//! 多网卡 / VPN 可能把全局广播路由到虚拟网卡;单播按板子 IP 选择局域网接口。
|
||||
//!
|
||||
//! 板子靠**源地址**记住客户端,所以客户端只要周期性续订就能维持推流,不存在任何
|
||||
//! 超时淘汰的竞态。
|
||||
@@ -59,7 +60,7 @@ use protocol::{
|
||||
/// 来自网络,而不是来自过小的缓冲。
|
||||
const RECV_BUFFER: usize = 2048;
|
||||
|
||||
/// 重新广播订阅的间隔。
|
||||
/// 广播及定向续订的间隔。
|
||||
///
|
||||
/// 比板子判定"客户端掉线"的超时(6 秒)短得多,所以偶发丢一两个包不会中断推流。
|
||||
const SUBSCRIBE_INTERVAL: Duration = Duration::from_millis(800);
|
||||
@@ -74,11 +75,68 @@ const FRAME_TIMEOUT: Duration = Duration::from_millis(500);
|
||||
pub struct CameraInfo {
|
||||
pub id: u32,
|
||||
pub addr: SocketAddrV4,
|
||||
pub firmware: u16,
|
||||
/// 板子自报的固件版本(来自它的 Hello)。
|
||||
pub firmware: FirmwareInfo,
|
||||
/// 最近一次收到它广播的时刻。
|
||||
pub last_seen: Instant,
|
||||
}
|
||||
|
||||
/// 板子自报的固件版本。
|
||||
///
|
||||
/// # 为什么不是一个 u16
|
||||
///
|
||||
/// 原来只有 `Hello.firmware: u16`,是个没有定义的数——显示出来也没人能解释。现在它是
|
||||
/// 一组**能对得上**的数:语义化版本、构建号、以及板子编译进去的线路协议版本。
|
||||
///
|
||||
/// 最后一项是这套机制的重点:一块烧了别的协议版本的板子,视频帧会被严格解析挡掉,
|
||||
/// 于是它在客户端看来等于不存在。但它的通告仍然读得出来,所以界面可以说
|
||||
/// "板子是协议 2,客户端是协议 1",而不是只能报"没有画面"。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct FirmwareInfo {
|
||||
/// 打包的语义化版本(`major << 16 | minor << 8 | patch`)。0 = 固件太老,没带。
|
||||
pub version: u32,
|
||||
/// 自增构建号。0 = 未知。
|
||||
pub build: u16,
|
||||
/// 板子自报的线路协议版本。
|
||||
pub protocol: u16,
|
||||
/// 旧的两字节字段原值,只用于日志。
|
||||
pub legacy: u16,
|
||||
}
|
||||
|
||||
impl FirmwareInfo {
|
||||
pub fn from_hello(hello: &protocol::Hello) -> Self {
|
||||
Self {
|
||||
version: hello.version,
|
||||
build: hello.build,
|
||||
protocol: hello.protocol,
|
||||
legacy: hello.firmware,
|
||||
}
|
||||
}
|
||||
|
||||
/// `"0.2.0"`;固件没带版本时返回 `None`(而不是 "0.0.0")。
|
||||
pub fn version_text(&self) -> Option<String> {
|
||||
(self.version != 0).then(|| protocol::format_version(self.version))
|
||||
}
|
||||
|
||||
/// 这块板子的协议和本客户端一致吗。
|
||||
///
|
||||
/// 老固件没有这个字段,`Hello::decode` 会把帧头里的版本号填进来——那个值已经被
|
||||
/// 严格解析校验过,所以到这一步一定匹配。
|
||||
pub fn is_compatible(&self) -> bool {
|
||||
self.protocol == protocol::PROTOCOL_VERSION
|
||||
}
|
||||
|
||||
/// 一行给人看的描述,日志与界面提示共用同一句。
|
||||
pub fn describe(&self) -> String {
|
||||
match (self.version_text(), self.build) {
|
||||
(Some(version), 0) => format!("v{version}"),
|
||||
(Some(version), build) => format!("v{version}.{build}"),
|
||||
(None, 0) => "版本未知".to_string(),
|
||||
(None, build) => format!("构建 {build}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CameraInfo {
|
||||
/// 多久没有广播了。
|
||||
pub fn silence(&self) -> Duration {
|
||||
@@ -243,15 +301,21 @@ impl Discovery {
|
||||
}
|
||||
}
|
||||
|
||||
fn note_hello(&self, id: u32, addr: SocketAddrV4, firmware: u16) -> bool {
|
||||
/// 记下一次通告。
|
||||
///
|
||||
/// 返回值只在**新出现**或**版本/协议发生变化**时有意义:`Some(true)` 表示它是兼容的,
|
||||
/// `Some(false)` 表示它自报的协议与本客户端不同。周期性的重复通告返回 `None`——
|
||||
/// 它每 2 秒来一次,每次都打日志会把真正的事件淹掉。
|
||||
fn note_hello(&self, id: u32, addr: SocketAddrV4, firmware: FirmwareInfo) -> Option<bool> {
|
||||
let mut cameras = self.cameras.lock().expect("cameras mutex poisoned");
|
||||
|
||||
match cameras.get_mut(&id) {
|
||||
Some(info) => {
|
||||
let changed = info.firmware != firmware;
|
||||
info.addr = addr;
|
||||
info.last_seen = Instant::now();
|
||||
info.firmware = firmware;
|
||||
false
|
||||
changed.then(|| firmware.is_compatible())
|
||||
}
|
||||
None => {
|
||||
cameras.insert(
|
||||
@@ -263,7 +327,7 @@ impl Discovery {
|
||||
last_seen: Instant::now(),
|
||||
},
|
||||
);
|
||||
true
|
||||
Some(firmware.is_compatible())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,8 +355,7 @@ impl Discovery {
|
||||
|
||||
/// 客户端身份。
|
||||
///
|
||||
/// 常量而非 MAC 派生:队伍客户端和裁判终端各自唯一,进程内也不需要区分自己,而一个
|
||||
/// 确定的常量让日志更好读。
|
||||
/// 旧客户端使用的固定标识,保留用于兼容判断。
|
||||
pub const CLIENT_ID: u32 = 0x0C11_0001;
|
||||
|
||||
/// 接收循环的可变状态:只由接收线程在跑循环时访问。
|
||||
@@ -318,6 +381,7 @@ pub struct Receiver {
|
||||
/// 身份可以运行时切换(同一个进程里从裁判端切到用户端),所以订阅里声明的角色
|
||||
/// 也得能改——它决定板子要不要给这个客户端推流。
|
||||
role: Mutex<SubscribeRole>,
|
||||
client_id: u32,
|
||||
camera: Ipv4Addr,
|
||||
stop: Arc<AtomicBool>,
|
||||
}
|
||||
@@ -327,6 +391,10 @@ impl Receiver {
|
||||
///
|
||||
/// `only_sender` 为 `Some` 时只接受那一块板子的帧。
|
||||
pub fn new(camera: Ipv4Addr, role: SubscribeRole, only_sender: Option<u32>) -> Self {
|
||||
let client_id = loop {
|
||||
let id = rand::random::<u32>();
|
||||
if id != 0 && id != CLIENT_ID { break id; }
|
||||
};
|
||||
Self {
|
||||
discovery: Arc::new(Discovery::new()),
|
||||
latest: Arc::new(LatestFrames::default()),
|
||||
@@ -337,6 +405,7 @@ impl Receiver {
|
||||
discovery_all: false,
|
||||
}),
|
||||
role: Mutex::new(role),
|
||||
client_id,
|
||||
camera,
|
||||
stop: Arc::new(AtomicBool::new(false)),
|
||||
}
|
||||
@@ -354,6 +423,15 @@ impl Receiver {
|
||||
) -> std::io::Result<(Self, UdpSocket)> {
|
||||
let socket =
|
||||
UdpSocket::bind(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, DEFAULT_PORT))?;
|
||||
// Absorb short scheduler/NIC bursts without losing JPEG fragments. The
|
||||
// reader still drains continuously; decoded video uses latest frames.
|
||||
let socket_ref = socket2::SockRef::from(&socket);
|
||||
if let Err(err) = socket_ref.set_recv_buffer_size(1024 * 1024) {
|
||||
log::warn!("could not enlarge the video receive buffer: {err}");
|
||||
}
|
||||
if let Ok(bytes) = socket_ref.recv_buffer_size() {
|
||||
log::info!("video UDP receive buffer: {bytes} bytes");
|
||||
}
|
||||
socket.set_read_timeout(Some(Duration::from_millis(100)))?;
|
||||
|
||||
let broadcast = socket.set_broadcast(true).is_ok();
|
||||
@@ -439,8 +517,28 @@ impl Receiver {
|
||||
}
|
||||
}
|
||||
|
||||
if self.discovery.note_hello(hello.sender_id, v4, hello.firmware) {
|
||||
log::info!("found camera 0x{:08X} at {v4}", hello.sender_id);
|
||||
let firmware = FirmwareInfo::from_hello(&hello);
|
||||
|
||||
if let Some(compatible) = self.discovery.note_hello(hello.sender_id, v4, firmware) {
|
||||
if compatible {
|
||||
log::info!(
|
||||
"found camera 0x{:08X} at {v4} ({}, protocol {})",
|
||||
hello.sender_id,
|
||||
firmware.describe(),
|
||||
firmware.protocol
|
||||
);
|
||||
} else {
|
||||
// 这条日志值钱的地方在于:它把"没有画面"翻译成了一个具体的原因。
|
||||
// 严格解析会把这些板子的视频帧全部丢掉,所以除了这里,没有任何地方
|
||||
// 会留下"场上有一块协议不匹配的板子"的痕迹。
|
||||
log::warn!(
|
||||
"camera 0x{:08X} at {v4} speaks protocol {}, this client speaks {} - \
|
||||
its video will be ignored; reflash the board with a matching firmware",
|
||||
hello.sender_id,
|
||||
firmware.protocol,
|
||||
protocol::PROTOCOL_VERSION
|
||||
);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -473,11 +571,17 @@ impl Receiver {
|
||||
.entry(id)
|
||||
.or_insert_with(|| FrameAssembler::new(FRAME_TIMEOUT));
|
||||
|
||||
match assembler.push(frag) {
|
||||
Ok(frame) => {
|
||||
let dropped_before = assembler.dropped_frames();
|
||||
let result = assembler.push(frag);
|
||||
let dropped = assembler.dropped_frames() - dropped_before;
|
||||
drop(ingest);
|
||||
self.discovery.count(id, |s| {
|
||||
s.datagrams += 1;
|
||||
s.frames_dropped += dropped;
|
||||
});
|
||||
|
||||
self.discovery.count(id, |s| s.datagrams += 1);
|
||||
match result {
|
||||
Ok(frame) => {
|
||||
self.discovery
|
||||
.note_frame(id, frame.width, frame.height, frame.data.len());
|
||||
|
||||
@@ -489,17 +593,7 @@ impl Receiver {
|
||||
sink(frame);
|
||||
}
|
||||
}
|
||||
Err(protocol::DropReason::Superseded) => {
|
||||
// 被更新的帧追上了。这不是丢帧:半截的那张只是被超越,在健康链路上
|
||||
// 这是帧被放弃的**正常**方式。
|
||||
self.discovery.count(id, |s| s.datagrams += 1);
|
||||
}
|
||||
Err(protocol::DropReason::TimedOut) => {
|
||||
self.discovery.count(id, |s| {
|
||||
s.datagrams += 1;
|
||||
s.frames_dropped += 1;
|
||||
});
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,6 +615,25 @@ impl Receiver {
|
||||
}
|
||||
}
|
||||
|
||||
/// 全局广播用于发现;已知板子还需按 IP 续订,避免广播被 VPN / 虚拟网卡接走。
|
||||
fn subscription_targets(&self) -> Vec<SocketAddrV4> {
|
||||
let mut targets = vec![SocketAddrV4::new(self.camera, DEFAULT_PORT)];
|
||||
let only_sender = self.sender_limit();
|
||||
|
||||
for camera in self.discovery.online_cameras() {
|
||||
if only_sender.is_some_and(|id| camera.id != id) {
|
||||
continue;
|
||||
}
|
||||
// Hello 的来源端口不一定是订阅监听端口;协议固定使用 DEFAULT_PORT。
|
||||
let target = SocketAddrV4::new(*camera.addr.ip(), DEFAULT_PORT);
|
||||
if !targets.contains(&target) {
|
||||
targets.push(target);
|
||||
}
|
||||
}
|
||||
|
||||
targets
|
||||
}
|
||||
|
||||
/// 运行接收循环,直到 [`Receiver::shutdown`]。
|
||||
///
|
||||
/// 阻塞,所以放在单独的线程上。
|
||||
@@ -543,12 +656,15 @@ impl Receiver {
|
||||
if last_subscribe.elapsed() >= SUBSCRIBE_INTERVAL {
|
||||
let subscribe = Subscribe {
|
||||
role: self.role(),
|
||||
client_id: CLIENT_ID,
|
||||
client_id: self.client_id,
|
||||
};
|
||||
|
||||
if let Err(err) = socket.send_to(&subscribe.encode(), target) {
|
||||
let encoded = subscribe.encode();
|
||||
for target in self.subscription_targets() {
|
||||
if let Err(err) = socket.send_to(&encoded, target) {
|
||||
log::warn!("could not subscribe at {target}: {err}");
|
||||
}
|
||||
}
|
||||
last_subscribe = Instant::now();
|
||||
}
|
||||
|
||||
@@ -635,10 +751,58 @@ mod tests {
|
||||
let hello = Hello {
|
||||
sender_id: sender,
|
||||
firmware: 7,
|
||||
version: protocol::pack_version(0, 2, 0),
|
||||
build: 57,
|
||||
protocol: protocol::PROTOCOL_VERSION,
|
||||
};
|
||||
rx.datagram(&hello.encode(MsgType::Hello), from(from_ip));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn discovered_boards_are_subscribed_directly_when_broadcast_uses_another_interface() {
|
||||
let rx = Receiver::new(Ipv4Addr::BROADCAST, SubscribeRole::Referee, None);
|
||||
let hello = Hello {
|
||||
sender_id: 1,
|
||||
..Default::default()
|
||||
};
|
||||
// Hello 可从其它端口发来,订阅仍需发往板子的固定监听端口。
|
||||
rx.datagram(&hello.encode(MsgType::Hello), "192.168.1.139:12345".parse().unwrap());
|
||||
feed_hello(&rx, 2, [192, 168, 1, 140]);
|
||||
|
||||
assert_eq!(rx.subscription_targets(), vec![
|
||||
"255.255.255.255:5600".parse().unwrap(),
|
||||
"192.168.1.139:5600".parse().unwrap(),
|
||||
"192.168.1.140:5600".parse().unwrap(),
|
||||
]);
|
||||
|
||||
// DHCP 地址变化后只使用新地址。
|
||||
feed_hello(&rx, 1, [192, 168, 1, 141]);
|
||||
assert_eq!(rx.subscription_targets(), vec![
|
||||
"255.255.255.255:5600".parse().unwrap(),
|
||||
"192.168.1.141:5600".parse().unwrap(),
|
||||
"192.168.1.140:5600".parse().unwrap(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn discovery_of_other_teams_does_not_directly_subscribe_to_their_video() {
|
||||
let rx = Receiver::new(Ipv4Addr::BROADCAST, SubscribeRole::Team, Some(1));
|
||||
rx.set_discovery_all(true);
|
||||
feed_hello(&rx, 1, [192, 168, 1, 10]);
|
||||
feed_hello(&rx, 2, [192, 168, 1, 20]);
|
||||
|
||||
assert_eq!(rx.discovery().cameras().len(), 2);
|
||||
assert_eq!(rx.subscription_targets(), vec![
|
||||
"255.255.255.255:5600".parse().unwrap(),
|
||||
"192.168.1.10:5600".parse().unwrap(),
|
||||
]);
|
||||
rx.restrict_to(Some(2));
|
||||
assert_eq!(rx.subscription_targets(), vec![
|
||||
"255.255.255.255:5600".parse().unwrap(),
|
||||
"192.168.1.20:5600".parse().unwrap(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delivers_the_latest_frame_of_the_bound_sender_only() {
|
||||
let rx = Receiver::new(Ipv4Addr::UNSPECIFIED, SubscribeRole::Team, Some(1));
|
||||
@@ -761,4 +925,21 @@ mod tests {
|
||||
|
||||
assert!(rx.discovery().cameras().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loss_statistics_include_superseded_frames_and_entirely_missing_ids() {
|
||||
let rx = Receiver::new(Ipv4Addr::UNSPECIFIED, SubscribeRole::Referee, None);
|
||||
feed_hello(&rx, 1, [192, 168, 1, 10]);
|
||||
feed_frame(&rx, 1, 1, [192, 168, 1, 10]);
|
||||
let partial = fragment_frame(1, 2, &jpeg(), 640, 480);
|
||||
rx.datagram(&partial[0].encode(), from([192, 168, 1, 10]));
|
||||
assert_eq!(rx.discovery().stats(1).frames_dropped, 0, "normal pending fragments are not drops");
|
||||
feed_frame(&rx, 1, 4, [192, 168, 1, 10]);
|
||||
let stats = rx.discovery().stats(1);
|
||||
assert_eq!(stats.frames_shown, 2);
|
||||
assert_eq!(stats.frames_dropped, 2, "partial 2 and entirely missing 3");
|
||||
assert_eq!(stats.loss_percent(), 50.0);
|
||||
for frag in partial { rx.datagram(&frag.encode(), from([192, 168, 1, 10])); }
|
||||
assert_eq!(rx.discovery().stats(1).frames_dropped, 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,17 @@ pub struct CameraDto {
|
||||
pub addr: String,
|
||||
pub online: bool,
|
||||
pub silence_ms: u64,
|
||||
pub firmware: u16,
|
||||
/// 固件版本字符串(`"0.2.0"`)。固件太老、没带这个字段时为 `null`。
|
||||
pub firmware_version: Option<String>,
|
||||
/// 自增构建号。0 = 未知。
|
||||
pub firmware_build: u16,
|
||||
/// 板子自报的线路协议版本。
|
||||
pub protocol: u16,
|
||||
/// 本客户端能不能和它通信。
|
||||
///
|
||||
/// 为 `false` 时它不会有画面——帧被严格解析挡掉了——但界面仍然能说出原因,
|
||||
/// 这就是这个字段存在的全部意义。
|
||||
pub compatible: bool,
|
||||
}
|
||||
|
||||
impl From<&CameraInfo> for CameraDto {
|
||||
@@ -43,7 +53,10 @@ impl From<&CameraInfo> for CameraDto {
|
||||
addr: camera.addr.ip().to_string(),
|
||||
online: camera.is_online(),
|
||||
silence_ms: camera.silence().as_millis() as u64,
|
||||
firmware: camera.firmware,
|
||||
firmware_version: camera.firmware.version_text(),
|
||||
firmware_build: camera.firmware.build,
|
||||
protocol: camera.firmware.protocol,
|
||||
compatible: camera.firmware.is_compatible(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+18
-1
@@ -3,7 +3,24 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>cam-desktop</title>
|
||||
<meta name="color-scheme" content="dark" />
|
||||
<title>RMIC::2026</title>
|
||||
|
||||
<!-- 中文三字重。这些文件是 cn-font-split 生成的(每种 82 个 unicode-range 分片),
|
||||
放在 public/fonts 里随产物一起走;不经过打包器,所以用绝对路径直接 link。
|
||||
放在 <head> 而不是 CSS 里 @import:@import 会串行阻塞,而这几份互不依赖。 -->
|
||||
<link rel="stylesheet" href="/fonts/harmonyos-sans-sc/Regular.css" />
|
||||
<link rel="stylesheet" href="/fonts/harmonyos-sans-sc/Medium.css" />
|
||||
<link rel="stylesheet" href="/fonts/harmonyos-sans-sc/Bold.css" />
|
||||
|
||||
<!-- 首屏所有数字都是拉丁字形,先把它预取下来;中文字体是按需分片,预取哪一个都是猜。 -->
|
||||
<link
|
||||
rel="preload"
|
||||
href="/fonts/jetbrains-mono/jetbrains-mono-latin-400-normal.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
crossorigin
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
{
|
||||
"name": "cam-desktop",
|
||||
"name": "rmic-2026",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.2",
|
||||
"type": "module",
|
||||
"description": "ESP32 图传与装甲板系统的桌面端:用户端(血条 + 瞄准器)与裁判端(双路 + 装甲板 + 录制)",
|
||||
"description": "RMIC::2026 —— ESP32 图传与装甲板系统的桌面端:用户端(装甲条 + 瞄准器)与裁判端(双路 + 装甲板 + 录制)",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc --noEmit && vite build",
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri",
|
||||
"app": "tauri dev",
|
||||
"check": "tsc --noEmit"
|
||||
"check": "tsc --noEmit",
|
||||
"version:write": "node ../scripts/version.mjs --write",
|
||||
"version:check": "node ../scripts/version.mjs --check",
|
||||
"test:stream": "node --test scripts/test-stream.mjs",
|
||||
"test:latency": "node --test scripts/test-latency-marker.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-dialog": "^2.8.3",
|
||||
"react": "^19",
|
||||
"react-dom": "^19"
|
||||
},
|
||||
|
||||
Generated
+10
@@ -11,6 +11,9 @@ importers:
|
||||
'@tauri-apps/api':
|
||||
specifier: ^2
|
||||
version: 2.12.3
|
||||
'@tauri-apps/plugin-dialog':
|
||||
specifier: ^2.8.3
|
||||
version: 2.8.3
|
||||
react:
|
||||
specifier: ^19
|
||||
version: 19.3.0
|
||||
@@ -529,6 +532,9 @@ packages:
|
||||
engines: {node: '>= 10'}
|
||||
hasBin: true
|
||||
|
||||
'@tauri-apps/plugin-dialog@2.8.3':
|
||||
resolution: {integrity: sha512-Hd/yBfk2R5v2MFrz/0VArSXeh8xKLLXGunqSiTtcHKn1zNZwaKujuA0VSwUvNfwC4hDoF1PveS+gBDNe3nHspw==}
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
||||
|
||||
@@ -1090,6 +1096,10 @@ snapshots:
|
||||
'@tauri-apps/cli-win32-ia32-msvc': 2.12.2
|
||||
'@tauri-apps/cli-win32-x64-msvc': 2.12.2
|
||||
|
||||
'@tauri-apps/plugin-dialog@2.8.3':
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.12.3
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
dependencies:
|
||||
'@babel/parser': 7.29.9
|
||||
|
||||
@@ -0,0 +1,680 @@
|
||||
/* Generated By cn-font-split@6.0.3 https://www.npmjs.com/package/cn-font-split
|
||||
CreateTime: Fri, 06 Jun 2025 12:25:03 GMT;
|
||||
Origin File Name Table:
|
||||
256: Clock Tabular
|
||||
257: Clock Proportional
|
||||
258: Calculator Operators
|
||||
259: Alternate “l”
|
||||
260: Full Width Math Symbols
|
||||
261: Punctuation Change
|
||||
262: Red Circle Fig
|
||||
263: Vert Figures
|
||||
copyright: Copyright 2021-2025 Huawei Device Co., Ltd. All Rights Reserved.
|
||||
fontFamily: HarmonyOS Sans SC
|
||||
fontSubfamily: Bold
|
||||
uniqueID: HarmonyOS Sans SC Bold Version 2.400
|
||||
fullName: HarmonyOS Sans SC Bold
|
||||
version: Version 2.400
|
||||
postScriptName: HarmonyOS_Sans_SC_Bold
|
||||
manufacturer: Hanyi Fonts
|
||||
designer: Huawei Device Co., Ltd & Hanyi Fonts
|
||||
preferredFamily: HarmonyOS Sans SC
|
||||
preferredSubfamily: Bold
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_53d21e.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+251,U+259,U+261,U+292,U+2bb-2bc,U+2ca-2cb,U+2d8-2dd,U+2f3,U+301,U+303,U+306-30a,U+30f,U+323,U+326-327,U+335,U+338,U+1e00-1e01,U+1e3e-1e3f,U+1e80-1e85,U+1ea0-1ef9,U+2004-2006,U+2009-200a,U+2010-2011,U+2013-201e,U+2020-2022,U+2025-2029,U+2030,U+2032-2037,U+2039-203b,U+2044,U+2052,U+2060-2064,U+2066-2069,U+2070,U+2074-2079,U+207f-2089,U+20a3-20a4,U+20a6-20ac,U+20b1,U+20b9-20ba,U+20bc-20bd,U+2103,U+2105,U+2109,U+2113,U+2116,U+2121-2122,U+2126,U+212e,U+215b-215e,U+2160-216b,U+2170-2179,U+2190-2193,U+2196-2199,U+2202,U+2206,U+2208,U+220f,U+2211-2212,U+2215,U+221a,U+221d-2220,U+2223,U+2225,U+2227-222b,U+222e,U+2234-2237,U+223d,U+2248,U+224c,U+2252,U+2260-2261,U+2264-2267,U+226e-226f,U+2295,U+2299,U+22a5,U+22bf,U+2312,U+2460-2469,U+2474-249b,U+2500-254b,U+2550-2573,U+2581-258f,U+2593-2595,U+25a0-25a1,U+25b2-25b3,U+25bc-25bd,U+25c6-25c7,U+25ca-25cb,U+25ce-25cf,U+25e2-25e5,U+2605-2606,U+2609,U+2640,U+2642,U+9f42-9f43,U+9f45-9f46,U+9f48-9f49,U+9f4c-9f4d,U+9f53,U+9f55-9f58,U+9f5a-9f5b,U+9f5d-9f5e,U+9f62,U+9f64-9f65,U+9f68-9f69,U+9f6b,U+9f6d-9f71,U+9f73-9f75,U+9f78-9f7e,U+9f81-9f82,U+9f8e-9f8f,U+9f91-9f93,U+9f96-9f98,U+9f9d-9f9e,U+9fa1-9fff,U+f9f1,U+fa0e-fa0f,U+fa11,U+fa13-fa14,U+fa1f,U+fa21,U+fa23-fa24,U+fa27-fa29,U+fe49-fe52,U+fe54-fe57,U+fe59-fe66,U+fe68-fe6b,U+ff01-ff03,U+ff05-ff0a,U+ff0c-ff1b,U+ff1f-ff3d,U+ff3f,U+ff41-ff5b,U+ff5d,U+ff5f-ff60,U+ffe6;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_70874c.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+9db2-9db3,U+9db5-9dba,U+9dbd-9dbf,U+9dc1,U+9dc3-9dd2,U+9dd4-9dd6,U+9dd8,U+9ddb-9de4,U+9de7-9dee,U+9df0-9df1,U+9df4-9df7,U+9dfb-9e0b,U+9e0d-9e14,U+9e16-9e19,U+9e1c,U+9e24,U+9e27,U+9e2e,U+9e30,U+9e34,U+9e3b-9e3c,U+9e40,U+9e4d,U+9e50,U+9e52-9e54,U+9e56,U+9e59,U+9e5d,U+9e5f-9e62,U+9e65,U+9e6e-9e6f,U+9e72,U+9e74-9e78,U+9e7b,U+9e80-9e81,U+9e83-9e84,U+9e86,U+9e89-9e8a,U+9e8c-9e91,U+9e94-9e96,U+9e98-9e9c,U+9e9e,U+9ea0-9ea4,U+9ea7-9ea8,U+9eaa-9eb3,U+9eb5-9eb7,U+9eb9-9eba,U+9ebf-9ec2,U+9ec5-9ec8,U+9eca-9ecb,U+9ed0,U+9ed3,U+9ed5-9ed7,U+9ed9-9eda,U+9ee1,U+9ee3-9ee4,U+9ee6,U+9eeb-9eee,U+9ef0-9ef1,U+9ef3,U+9ef5-9ef6,U+9ef8,U+9efa,U+9f00-9f08,U+9f0a,U+9f0c,U+9f0f,U+9f11-9f12,U+9f14,U+9f16,U+9f18,U+9f1a-9f1f,U+9f23-9f2b,U+9f2d-9f2e,U+9f30-9f33,U+9f35-9f36,U+9f38,U+9f3a,U+9f3c,U+9f3f-9f41;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_d2da49.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+9c2f-9c30,U+9c34,U+9c36,U+9c38,U+9c3a,U+9c3c-9c3d,U+9c3f-9c44,U+9c46-9c47,U+9c4a-9c51,U+9c53,U+9c55,U+9c59-9c5c,U+9c5e,U+9c60-9c66,U+9c68-9c6c,U+9c6e-9c76,U+9c79,U+9c7b,U+9c7d-9c7e,U+9c80,U+9c83-9c84,U+9c89-9c8a,U+9c8c,U+9c8f,U+9c93,U+9c96-9c99,U+9c9d,U+9caa,U+9cac,U+9caf,U+9cb9,U+9cbe-9cc2,U+9cc8-9cc9,U+9cd1-9cd2,U+9cda-9cdb,U+9ce0-9ce1,U+9ce3-9ce4,U+9ce6,U+9ce8,U+9cea-9cf2,U+9cf5,U+9cf7-9d05,U+9d08,U+9d0a-9d14,U+9d16-9d1a,U+9d1c,U+9d1e,U+9d20-9d22,U+9d24-9d25,U+9d27,U+9d29-9d2e,U+9d31-9d3a,U+9d3c-9d3e,U+9d40-9d41,U+9d43-9d50,U+9d54-9d5b,U+9d5e-9d5f,U+9d62-9d69,U+9d6b,U+9d6d-9d6e,U+9d70-9d71,U+9d73-9d86,U+9d88,U+9d8a-9d92,U+9d94-9d97,U+9d99,U+9d9b-9da4,U+9da6-9da8,U+9daa-9dae,U+9db0-9db1;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_e8003a.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+9ac8-9aca,U+9acd-9ace,U+9ad0,U+9ad7,U+9ad9-9ade,U+9ae0,U+9ae2-9ae5,U+9ae7-9aea,U+9aec,U+9aee,U+9af0-9af8,U+9afa,U+9afc-9b02,U+9b04-9b05,U+9b07,U+9b09-9b0e,U+9b10-9b12,U+9b14-9b1e,U+9b20-9b21,U+9b24,U+9b26,U+9b2a-9b2d,U+9b30,U+9b33-9b3a,U+9b3d-9b40,U+9b46,U+9b4a-9b4c,U+9b50,U+9b52-9b53,U+9b55-9b57,U+9b59,U+9b5b-9b6e,U+9b70-9b73,U+9b75-9b76,U+9b78-9b80,U+9b82,U+9b84-9b8d,U+9b8f,U+9b93-9b99,U+9b9b-9b9d,U+9b9f-9ba9,U+9bac,U+9baf-9bbf,U+9bc2-9bc6,U+9bc8,U+9bcb-9bd3,U+9bd5,U+9bd8-9bda,U+9bdc,U+9bde-9be0,U+9be3,U+9be5-9be6,U+9be9,U+9bec-9bef,U+9bf1-9bf3,U+9bf5-9bfc,U+9bfe,U+9c00-9c07,U+9c0a-9c0c,U+9c0e-9c11,U+9c14-9c1f,U+9c21-9c22,U+9c24,U+9c26-9c27,U+9c2a-9c2c,U+9c2e;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_bba9bc.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+98c7,U+98c9-98cd,U+98cf-98d0,U+98d4,U+98d6-98d7,U+98dc-98dd,U+98e1,U+98e3-98e6,U+98ec,U+98ee,U+98f0,U+98f3,U+98f5-98fb,U+98ff-9902,U+9904,U+9906-9907,U+990b,U+990e-990f,U+9914-9917,U+9919,U+991c-991d,U+991f-9920,U+9922-9927,U+9929-992b,U+992d,U+992f-9930,U+9932,U+9934,U+9936,U+9938-993a,U+993d,U+9940-9942,U+9944,U+9946-9947,U+994d-9951,U+9953,U+9956,U+9958-995b,U+995d,U+995f-9961,U+9964,U+9966,U+9973,U+9978-9979,U+997b,U+997e,U+9982-9983,U+9989,U+998c,U+998e,U+999a-99a4,U+99a6-99a7,U+99a9-99ab,U+99af-99b0,U+99b2,U+99b5-99c0,U+99c2-99cf,U+99d3,U+99d6-99d7,U+99da,U+99dc,U+99de,U+99e0-99e1,U+99e3-99ec,U+99ee-99f0,U+99f2-99fe,U+9a00,U+9a02-9a04,U+9a06-9a0c,U+9a10-9a15,U+9a17-9a18,U+9a1a-9a2a,U+9a2c,U+9a2f,U+9a31-9a35,U+9a39-9a3d,U+9a3f,U+9a46-9a49,U+9a4b-9a4c,U+9a4e,U+9a50-9a54,U+9a56,U+9a58-9a59,U+9a5c-9a5e,U+9a60-9a61,U+9a63,U+9a66-9a69,U+9a6b,U+9a72,U+9a83,U+9a89,U+9a8d-9a8e,U+9a94-9a95,U+9a99,U+9aa6,U+9aa9-9aae,U+9ab2-9ab5,U+9ab9,U+9abb,U+9abd-9abf,U+9ac3-9ac4,U+9ac6-9ac7;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_0ca5a9.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+96e1,U+96e4-96e7,U+96eb-96ee,U+96f0-96f1,U+96f4-96f5,U+96f8,U+96fa,U+96fc-96fd,U+96ff,U+9702-9703,U+9705,U+970a-970c,U+9710,U+9712,U+9714-9715,U+9717-971b,U+971d,U+971f-9726,U+9728-9729,U+972b-972c,U+972e-972f,U+9731,U+9733-9737,U+973a-973c,U+973f-9741,U+9743,U+9745-9747,U+9749-9751,U+9754-9755,U+9757-9758,U+975d,U+975f,U+9763-9764,U+9767,U+976a-9772,U+9775,U+9777-977b,U+977d-9784,U+9786-978a,U+978c,U+978e,U+9790,U+9793,U+9795-9797,U+9799-979c,U+979e-979f,U+97a1-97a2,U+97a4-97a5,U+97a7-97aa,U+97ac,U+97ae,U+97b0-97b1,U+97b3,U+97b5-97bc,U+97be-97c2,U+97c4-97c5,U+97c7-97c8,U+97ca,U+97cd-97d2,U+97d4-97d8,U+97da-97db,U+97df-97e5,U+97e8,U+97ee-97f2,U+97f4,U+97f7-97fa,U+97fc-97fe,U+9800,U+9804,U+9809,U+980b,U+980d,U+9814-9816,U+9819-981b,U+981d-9820,U+9822-9823,U+9825,U+9827-982c,U+982e-982f,U+9831-9836,U+983a,U+983c-9845,U+9847-984b,U+9850-9852,U+9854-9855,U+9857,U+985a,U+985c-985d,U+985f-9861,U+9863-9864,U+9866,U+9868-986a,U+986d-986e,U+9872,U+988b,U+988e,U+9892,U+9895,U+9899,U+98a3,U+98a9-98ad,U+98b0-98b2,U+98b4-98b5,U+98b7-98bb,U+98bd-98c3,U+98c5;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_22048b.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+9213,U+9216-921d,U+921f-9222,U+9224,U+9228-922d,U+922f,U+9231-9232,U+9235-9236,U+923b-923c,U+9241-9244,U+9246-9247,U+924a-924c,U+924e-9250,U+9252-9254,U+9256,U+9258-9259,U+925c-925d,U+925f-9261,U+9263,U+9265,U+9267-926b,U+926e-9273,U+9275-9277,U+9279,U+927c-927e,U+9281-9282,U+9284,U+9286-928d,U+928f-9290,U+9292,U+9294-9295,U+9297,U+9299,U+929b,U+929d-929f,U+92a1-92a2,U+92a4,U+92a7,U+92ad,U+92af-92b0,U+92b2,U+92b4-92b6,U+92b8-92ba,U+92bd-92c0,U+92c2,U+92c4,U+92c6,U+92c9-92cb,U+92cd-92ce,U+92d0-92d1,U+92d3-92dc,U+92de,U+92e0-92e2,U+92e7,U+92e9,U+92eb-92ed,U+92f2-92f5,U+92f7,U+92f9-92fb,U+92fd-9300,U+9302-9303,U+9305,U+9309-930f,U+9311,U+9313-9314,U+9316-9317,U+931c-931e,U+9321,U+9323-9325,U+9327,U+9329-932a,U+932c-932d,U+9330-9332,U+9334-9335,U+9337,U+9339-933d,U+933f,U+9342,U+9344-9345,U+9348-934a,U+934c,U+934e-9353,U+9355-9357,U+9359-935a,U+935c-9363,U+9366-9368,U+936b,U+936d-936f,U+9371-9374,U+9377-9379,U+937b-937d,U+937f-9381,U+9383,U+9385-9386,U+9388-9389,U+938b-938e,U+9390-9395,U+9397,U+9399-93a1,U+93a4-93a5,U+93a8,U+93ab,U+93ad,U+93af,U+93b1-93b2,U+93b4,U+93b6-93be,U+93c0-93c2,U+93c4-93c6,U+93c9,U+93cb,U+93ce-93d0,U+93d2-93d5,U+93d9-93db,U+93e0,U+93e3,U+93e5;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_3c428d.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+93e6-93e7,U+93e9-93f4,U+93f6,U+93f8,U+93fa-93fc,U+93fe-9402,U+9404-940a,U+940c-940f,U+9411,U+9415-9418,U+941a-941f,U+9421-9425,U+9429-942a,U+942c-942d,U+942f-9431,U+9434,U+9436-9437,U+9439,U+943b-943d,U+9440-9443,U+9445-9449,U+944b,U+944d-9450,U+9452-9453,U+9455-945f,U+9461-9462,U+9464,U+9466-946a,U+946c,U+946e-946f,U+9471,U+9473-9476,U+9478,U+947a-947b,U+9480,U+9482-9484,U+9491,U+9496,U+9498,U+94c7,U+94cf,U+94d3-94d4,U+94da,U+94e6,U+94fb,U+951c,U+9520,U+9527,U+9533,U+953d,U+9543,U+9548,U+954b,U+9555,U+955a,U+9560,U+956e,U+9574-9575,U+9578-957e,U+9581,U+9584-9585,U+9587-9588,U+958a,U+958d,U+9590-9591,U+9595-9597,U+9599-95a0,U+95a2,U+95a6-95a7,U+95aa,U+95ae-95b0,U+95b2-95b5,U+95b7-95b8,U+95ba,U+95c0-95c2,U+95c4-95c5,U+95c9,U+95cd-95cf,U+95d1-95d3,U+95d7-95db,U+95dd,U+95df-95e0,U+95e3-95e4,U+95e6-95e7,U+95ec,U+95ff,U+9607,U+9613,U+9618,U+961b,U+961e,U+9620,U+9623-9629,U+962b-962d,U+962f-9630,U+9637-963a,U+963e,U+9641,U+9643,U+964a,U+964e-964f,U+9651-9653,U+9656-9657,U+9659-965a,U+965c,U+965e,U+9660,U+9665-9666,U+966b,U+966d-966f,U+9671,U+9679-967c,U+967e-9684,U+9687,U+968c,U+9691-9693,U+9696,U+969a,U+969d-96a6,U+96a9,U+96ab-96af,U+96b2,U+96b5,U+96b7,U+96ba,U+96bf,U+96c2-96c3,U+96ca,U+96d0-96d1,U+96d3-96d4,U+96d7-96d8,U+96da,U+96dd,U+96df;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_d9776c.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+8f2e,U+8f30-8f32,U+8f34-8f37,U+8f39-8f3a,U+8f3c-8f3d,U+8f40-8f41,U+8f43,U+8f47-8f48,U+8f4a-8f4c,U+8f4f-8f53,U+8f55-8f5e,U+8f60,U+8f63,U+8f65,U+8f6a,U+8f80,U+8f8c,U+8f92,U+8f9d,U+8fa0-8fa2,U+8fa4-8fa5,U+8fa7,U+8faa,U+8fac,U+8fb3-8fb5,U+8fb7-8fb8,U+8fba-8fbc,U+8fbf-8fc0,U+8fc3,U+8fc6,U+8fc9-8fcd,U+8fcf,U+8fd2,U+8fd6-8fd7,U+8fda,U+8fe0-8fe1,U+8fe3,U+8fe7,U+8fec,U+8fef,U+8ff1-8ff2,U+8ff5-8ff6,U+8ffa-8ffc,U+8ffe-8fff,U+9007-9008,U+900c,U+900e,U+9013,U+9018,U+901c,U+9024-9025,U+9027-902b,U+9030-9031,U+9033-9034,U+9037,U+9039-903a,U+903d,U+903f-9040,U+9043,U+9045-9046,U+9048-9049,U+904c,U+9056,U+905a,U+905f,U+9061,U+9064,U+9066-9067,U+906a-906c,U+906f-9071,U+9073,U+9076,U+9079,U+907b,U+907e,U+9085-9086,U+9089,U+908c-908e,U+9092,U+9094,U+9096,U+9098,U+909a,U+909c,U+909e-90a0,U+90a4-90a5,U+90a7-90a9,U+90ab,U+90ad,U+90b2,U+90b7,U+90bc-90bd,U+90bf-90c0,U+90c2-90c3,U+90c6,U+90c8-90c9,U+90cb-90cd,U+90d2,U+90d4-90d6,U+90d8-90da,U+90de,U+90e0,U+90e3,U+90e5,U+90e9-90ea,U+90ec,U+90ee,U+90f0-90f3,U+90f6-90f7,U+90f9-90fc,U+90ff-9101,U+9103,U+9105,U+9107-9108,U+910a-9111,U+9113,U+9115,U+9117-9118,U+911a-911d,U+911f-9121,U+9124-9126,U+9128-912c,U+912e,U+9133,U+9135,U+9137-9138,U+913b-9142,U+9144-9145,U+9147,U+9151,U+9153-9156,U+9158-9159,U+915b-915c,U+915f-9160,U+9166-9168,U+916b,U+916d,U+9173,U+917a-917c,U+9180-9182,U+9184,U+9186,U+9188,U+918a,U+918e-918f,U+9193-9199,U+919d,U+919f-91a1,U+91a4-91a9,U+91b0-91b3,U+91b6-91b9,U+91bb-91bf,U+91c2,U+91c4,U+91c6,U+91c8,U+91d6,U+91da-91db,U+91de-91e2,U+91e5,U+91e8,U+91ea-91f4,U+91f6,U+91f8,U+91fb-91ff,U+9202-9203,U+9205-9207,U+920a-920c,U+920e-920f,U+9212;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_09158a.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+8cdf,U+8ce5,U+8ce8-8ce9,U+8ceb,U+8cee-8cf3,U+8cf5-8cf9,U+8cff-8d03,U+8d06-8d07,U+8d09,U+8d0b-8d0c,U+8d0e,U+8d11-8d12,U+8d14-8d15,U+8d18-8d1a,U+8d1c,U+8d20,U+8d51-8d52,U+8d57,U+8d5f,U+8d65,U+8d68-8d6a,U+8d6c,U+8d6e-8d6f,U+8d71-8d72,U+8d78-8d80,U+8d82-8d83,U+8d86-8d89,U+8d8c-8d90,U+8d92-8d93,U+8d96-8d98,U+8d9a-8d9e,U+8da0-8da2,U+8da4-8da7,U+8da9-8db0,U+8db6-8db7,U+8db9,U+8dbb,U+8dbd,U+8dc0-8dc2,U+8dc5,U+8dc7-8dca,U+8dcd,U+8dd0,U+8dd2-8dd5,U+8dd8-8dd9,U+8ddc,U+8de0,U+8de2,U+8de5-8de7,U+8de9,U+8ded-8dee,U+8df0-8df2,U+8df4,U+8df6,U+8dfc,U+8dfe-8e04,U+8e06-8e08,U+8e0b,U+8e0d-8e0e,U+8e11-8e13,U+8e15-8e1c,U+8e20-8e21,U+8e24-8e28,U+8e2b,U+8e2d,U+8e32-8e33,U+8e36-8e38,U+8e3b-8e3c,U+8e3e-8e3f,U+8e43,U+8e45-8e46,U+8e4d-8e50,U+8e53-8e54,U+8e56-8e58,U+8e5a-8e5f,U+8e61-8e62,U+8e65,U+8e67-8e68,U+8e6a-8e6b,U+8e6e,U+8e71,U+8e73,U+8e75,U+8e77-8e79,U+8e7b,U+8e7d-8e7e,U+8e80,U+8e82-8e84,U+8e86,U+8e88,U+8e8c,U+8e8e,U+8e96-8e99,U+8e9b,U+8e9d,U+8e9f-8ea0,U+8ea2-8ea4,U+8ea7-8ea9,U+8ead-8eae,U+8eb0-8eb1,U+8eb3-8eb9,U+8ebb-8ebf,U+8ec1-8ec9,U+8ecf-8ed1,U+8ed3,U+8ed5-8eda,U+8edc-8ede,U+8ee0-8ee3,U+8ee5-8eea,U+8eec-8ef1,U+8ef3-8ef7,U+8efd,U+8eff-8f02,U+8f04,U+8f06,U+8f08,U+8f0b-8f11,U+8f16-8f1a,U+8f20-8f24,U+8f27-8f28,U+8f2b-8f2d;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_a0f240.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+8a4a-8a4d,U+8a4f,U+8a51,U+8a53,U+8a56-8a57,U+8a59-8a5a,U+8a5c-8a5d,U+8a5f,U+8a64-8a65,U+8a67-8a68,U+8a6a,U+8a6f,U+8a74,U+8a76-8a78,U+8a7a-8a7b,U+8a7d-8a7e,U+8a80-8a83,U+8a88,U+8a8b,U+8a8e-8a90,U+8a94,U+8a96-8a97,U+8a99,U+8a9b-8a9d,U+8a9f,U+8aa2,U+8aa7,U+8aa9,U+8aab-8aaf,U+8ab1,U+8ab3-8ab5,U+8ab7-8ab8,U+8aba-8abb,U+8abd-8abe,U+8ac0-8ac1,U+8ac3,U+8ac5-8ac6,U+8ac8,U+8aca,U+8acc,U+8ace,U+8ad0,U+8ad3-8ad5,U+8ad8-8ada,U+8add,U+8adf-8ae0,U+8ae3,U+8ae5,U+8ae8-8aea,U+8aec,U+8aef-8af0,U+8af2,U+8af4-8af5,U+8af9,U+8afb,U+8afd,U+8aff,U+8b03,U+8b06,U+8b08-8b09,U+8b0b-8b0d,U+8b0f,U+8b11-8b13,U+8b15,U+8b18,U+8b1a,U+8b1c,U+8b1e-8b1f,U+8b21-8b25,U+8b27,U+8b29-8b2a,U+8b2d-8b32,U+8b34-8b38,U+8b3a-8b3d,U+8b3f-8b40,U+8b42-8b48,U+8b4a-8b4d,U+8b50-8b55,U+8b57,U+8b5b,U+8b5d-8b5e,U+8b60-8b65,U+8b67-8b6a,U+8b6d-8b6e,U+8b71-8b73,U+8b75-8b76,U+8b78-8b7c,U+8b7f,U+8b81-8b89,U+8b8b-8b8d,U+8b8f-8b91,U+8b94,U+8b97-8b9b,U+8b9d,U+8b9f,U+8bac,U+8bb1,U+8bbb,U+8bc7,U+8bd0,U+8bea,U+8c09,U+8c1e,U+8c38-8c40,U+8c42-8c45,U+8c4a-8c4b,U+8c4d,U+8c4f,U+8c51-8c53,U+8c56-8c59,U+8c5b-8c60,U+8c63-8c69,U+8c6d-8c72,U+8c74-8c77,U+8c7b-8c81,U+8c83-8c84,U+8c86-8c88,U+8c8b,U+8c8e-8c92,U+8c95-8c97,U+8c99-8c9a,U+8c9c,U+8c9f,U+8ca3-8ca6,U+8cad-8cae,U+8cb1,U+8cb5,U+8cb9,U+8cbe,U+8cc6,U+8cc9,U+8ccb-8cd0,U+8cd4,U+8cd6-8cd9,U+8cdb,U+8cdd;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_e7152d.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+883f,U+8841-8843,U+8847-8849,U+884b,U+884e-8852,U+8856,U+8858,U+885a,U+885c,U+885e-8860,U+8866-8867,U+886a,U+886d,U+886f,U+8871,U+8873-8876,U+8878-887c,U+8880,U+8883,U+8886-8887,U+8889-888a,U+888c,U+888e-8891,U+8893-8895,U+8897-889b,U+889d,U+889f-88a1,U+88a3,U+88a5-88aa,U+88ac,U+88ae-88b0,U+88b2-88b6,U+88b8-88bb,U+88bd-88c0,U+88c3-88c4,U+88c7-88c8,U+88ca-88cd,U+88d0-88d1,U+88d3,U+88d6-88d7,U+88da-88db,U+88de,U+88e0-88e1,U+88e6-88e7,U+88e9-88ef,U+88f2,U+88f5-88f7,U+88fa-88fb,U+88ff-8901,U+8903-8909,U+890b-890f,U+8911,U+8914-8918,U+891c-8920,U+8922-8924,U+8926-8929,U+892c-892f,U+8931,U+8935,U+8937,U+8939-893a,U+893c-8940,U+8942-8943,U+8945,U+8947-8948,U+894a-8955,U+8957-895c,U+8961-8963,U+8965,U+8967-8969,U+896b-896e,U+8970-8971,U+8973-897a,U+897c-897e,U+8980,U+8982,U+8984-8985,U+8987,U+8989-898a,U+898c-898e,U+8990-8992,U+8994-8995,U+8997,U+8999-89a0,U+89a2-89a5,U+89a7-89a9,U+89ab,U+89ad-89ae,U+89b0-89b1,U+89b3-89b6,U+89b8-89b9,U+89bb-89bc,U+89be,U+89c3,U+89cd,U+89d3,U+89d5,U+89d7-89d9,U+89db,U+89dd,U+89df-89e2,U+89e4,U+89e7-89ea,U+89ec-89ee,U+89f0-89f2,U+89f5,U+89f7,U+89f9-89ff,U+8a04-8a06,U+8a09,U+8a0b,U+8a0d,U+8a0f,U+8a11-8a12,U+8a14,U+8a19-8a1a,U+8a1c,U+8a1e,U+8a20-8a22,U+8a24,U+8a26-8a29,U+8a2b-8a2c,U+8a2e-8a30,U+8a32-8a33,U+8a35,U+8a37-8a39,U+8a3c-8a3d,U+8a3f-8a40,U+8a42-8a45,U+8a47,U+8a49;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_d8e0ef.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+8641-864c,U+8652-8653,U+8656-8659,U+865d,U+8660-8661,U+8663-8666,U+8668-866a,U+866d,U+8670,U+8672-8678,U+8683-8689,U+868e-8692,U+8694,U+8696-869b,U+869e-86a2,U+86a5-86a6,U+86ab,U+86ad-86ae,U+86b2-86b3,U+86b7-86b9,U+86bb-86bf,U+86c1-86c3,U+86c5,U+86c8,U+86cc-86cd,U+86d2-86d3,U+86d5-86d7,U+86da,U+86dc-86dd,U+86e0-86e3,U+86e5-86e8,U+86ea-86ec,U+86ef,U+86f5-86f7,U+86fc-86fd,U+86ff,U+8701,U+8704-8705,U+870b-870c,U+870e-8711,U+8714,U+8719,U+871b,U+871d,U+871f-8720,U+8724,U+8726-8728,U+872a-872d,U+872f-8730,U+8732-8733,U+8735-8736,U+8738-873a,U+873c-873d,U+8740-8746,U+874a-874b,U+874d,U+874f-8752,U+8754,U+8756,U+8758,U+875a-875e,U+8761-8762,U+8767,U+8769-876d,U+876f,U+8771-8773,U+8775,U+8777,U+8779-877a,U+877f-8781,U+8786-8787,U+8789-878a,U+878c,U+878e-8792,U+8794-8796,U+8798-879d,U+87a0-87a1,U+87a3-87a7,U+87a9-87aa,U+87ae,U+87b0-87b2,U+87b4,U+87b6-87b9,U+87bc,U+87be-87bf,U+87c1-87c3,U+87c5,U+87c7,U+87c9,U+87cc-87cd,U+87cf-87d0,U+87d4-87da,U+87dc-87df,U+87e1-87e2,U+87e4,U+87e6-87e9,U+87eb,U+87ed,U+87f0-87f1,U+87f3-87f5,U+87f7-87f8,U+87fa,U+87fc-87fd,U+87ff-8802,U+8804,U+8807-8809,U+880b-880c,U+880e-880f,U+8812,U+8817-881a,U+881c-881e,U+8820,U+8824-8830,U+8833-8835,U+8837-8838,U+883a,U+883d-883e;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_0cdc77.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+848d,U+848f,U+8491-8493,U+8495-8496,U+8498,U+849a-849b,U+849d,U+849f-84a0,U+84a2-84ae,U+84b0-84b1,U+84b3,U+84b5-84b7,U+84bb,U+84be,U+84c2-84c3,U+84c5,U+84c7-84c8,U+84cc,U+84ce-84cf,U+84d2,U+84d4-84d5,U+84d7-84dc,U+84de,U+84e1-84e2,U+84e4,U+84e8-84eb,U+84ed,U+84f1-84f3,U+84f5-84fb,U+84fe,U+8500-850b,U+850d-8510,U+8512,U+8515-8516,U+8518-8519,U+851b-851d,U+8520,U+8522,U+8524,U+8527-852a,U+852e-8536,U+853e-8540,U+8542,U+8544-8545,U+8547,U+854b-854d,U+854f-8551,U+8554,U+8557,U+855a-855d,U+855f-8561,U+8563,U+8565-8567,U+856b-856c,U+856e-8571,U+8573,U+8575-8576,U+8578,U+857c-857d,U+857f-8583,U+8586,U+8589,U+858b,U+858d-858e,U+8590,U+8592-8593,U+8595-859a,U+859d-859e,U+85a0-85a3,U+85a5,U+85a7,U+85ab-85ad,U+85b1-85b6,U+85b8,U+85bb-85c0,U+85c2-85c8,U+85ca-85cc,U+85d1-85d2,U+85d4,U+85d6-85db,U+85de-85e3,U+85e6-85e8,U+85eb-85f5,U+85f7-85f8,U+85fc-85fe,U+8600-8603,U+8608-8609,U+860c-8610,U+8612-8615,U+8617-8619,U+861b-861d,U+861f-8621,U+8623-8626,U+8628,U+862a-862c,U+862e-8637,U+8639,U+863b,U+863d-863e,U+8640;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_2f7c7d.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+81ac-81b2,U+81b4-81b9,U+81bc,U+81c4-81c5,U+81c7-81c8,U+81cb,U+81ce,U+81d0-81d1,U+81d3-81d7,U+81d9,U+81db-81df,U+81e1-81e2,U+81e4,U+81e6,U+81e9,U+81eb,U+81ee-81f2,U+81f5-81fa,U+81fd,U+81ff,U+8203,U+820b,U+820e-820f,U+8211,U+8213,U+8215-821a,U+821d,U+8220,U+8224-8227,U+8229,U+822e,U+8232,U+823a,U+823c-823d,U+823f-8243,U+8245-8246,U+8248,U+824a,U+824c-824e,U+8250-8257,U+825b-825e,U+8260-8263,U+8265,U+8267,U+8269-826a,U+826c-826d,U+8275-8277,U+827b-827c,U+8280-8281,U+8283,U+8285-8287,U+8289,U+828c,U+8290,U+8293-8296,U+829a-829b,U+829e,U+82a0,U+82a2-82a3,U+82a7,U+82b2,U+82b5-82b6,U+82ba,U+82bc,U+82bf-82c0,U+82c2-82c3,U+82c5-82c6,U+82c9,U+82d0,U+82d6,U+82d9-82da,U+82dd,U+82e2,U+82e8-82ea,U+82ec-82ee,U+82f0,U+82f2-82f3,U+82f5-82f6,U+82f8,U+82fa,U+82fc-8300,U+830a-830b,U+830d,U+8310,U+8312-8313,U+8316,U+8318-8319,U+831d-8326,U+8329-832a,U+832e,U+8330,U+8337,U+833b,U+833d-833f,U+8341-8342,U+8344-8345,U+8348,U+834b-834e,U+8353,U+8355-8359,U+835d,U+8362,U+8370-8376,U+8379-837a,U+837e-8384,U+8387-8388,U+838b-838d,U+838f-8391,U+8394-8395,U+8397,U+8399-839a,U+839d,U+839f,U+83a1,U+83a3-83a6,U+83ac-83af,U+83b5,U+83bb,U+83be-83bf,U+83c2-83c4,U+83c6,U+83c8-83c9,U+83cb,U+83cd-83ce,U+83d0-83d2,U+83d5,U+83d7,U+83d9-83db,U+83de,U+83e2-83e4,U+83e6-83e8,U+83eb-83ee,U+83f3,U+83f5-83f7,U+83fa-83fc,U+83fe-8400,U+8402,U+8405,U+8408-8409,U+8410,U+8412-8417,U+8419-841b,U+841e-8423,U+8429-842b,U+842d-8430,U+8432-8434,U+8436-8437,U+8439-843b,U+843e-8445,U+8447-8448,U+844a-8450,U+8453-8456,U+8458,U+845d-8460,U+8462,U+8465,U+8467-8468,U+846a,U+846e-8470,U+8472,U+8474,U+8479,U+847b-8481,U+8483-8486,U+848a;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_138611.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+7e8b,U+7e8e,U+7e90-7e92,U+7e95,U+7e97,U+7e99-7e9a,U+7e9d-7e9e,U+7eae,U+7eb4,U+7ebb-7ebc,U+7ed6,U+7ee4,U+7eec,U+7ef9,U+7f0a,U+7f10,U+7f1e,U+7f37,U+7f39,U+7f3b-7f41,U+7f43,U+7f46-7f4b,U+7f4d-7f4f,U+7f52-7f53,U+7f56,U+7f59,U+7f5b-7f5e,U+7f60,U+7f63-7f67,U+7f6b-7f6d,U+7f6f,U+7f73,U+7f76,U+7f78,U+7f7a-7f7d,U+7f7f-7f80,U+7f82-7f84,U+7f87,U+7f89,U+7f8d,U+7f8f-7f93,U+7f95-7f99,U+7f9b-7f9c,U+7fa0,U+7fa2,U+7fa6,U+7faa-7fae,U+7fb1,U+7fb3-7fb5,U+7fb7,U+7fba-7fbb,U+7fbe,U+7fc0,U+7fc2-7fc4,U+7fc6-7fc9,U+7fcb,U+7fcd,U+7fcf-7fd1,U+7fd3,U+7fd6-7fd7,U+7fd9-7fde,U+7fe2-7fe4,U+7fe7-7fe8,U+7fea,U+7fec-7fed,U+7fef,U+7ff2,U+7ff4-7ff8,U+7ffd-7fff,U+8002,U+8007-800a,U+800e-800f,U+8011,U+8013,U+801a-801b,U+801d-801f,U+8021,U+8023-8024,U+802b,U+802d,U+802f-8030,U+8032,U+8034,U+8039-803a,U+803c,U+803e,U+8040-8041,U+8044-8045,U+8047-8049,U+804e-8051,U+8053,U+8055,U+8057,U+8059,U+805b-805d,U+805f-8068,U+806b-806e,U+8074,U+8078,U+807a-807c,U+8081-8082,U+8088,U+808a,U+808d-8092,U+8094-8095,U+8097,U+8099,U+809e,U+80a3,U+80a6-80a8,U+80ac,U+80b0,U+80b3,U+80b5-80b6,U+80b8-80b9,U+80bb,U+80c5,U+80c7-80cb,U+80cf-80d5,U+80d8,U+80df-80e0,U+80e2-80e3,U+80e6,U+80ee,U+80f5,U+80f7,U+80f9,U+80fb,U+80fe-8101,U+8103-8104,U+8107,U+810b-810c,U+8115,U+8117,U+8119,U+811c-811d,U+811f-8122,U+8124-8128,U+812a,U+812d-812e,U+8130,U+8133-8135,U+8137,U+813a-813d,U+813f-8140,U+8142-8145,U+8147,U+8149,U+814d,U+814f,U+8152,U+8157-8158,U+815b-815f,U+8162-8164,U+8168,U+816a,U+816c,U+816f,U+8172,U+8175-8177,U+8184-8187,U+8189,U+818b-818e,U+8190,U+8192-8197,U+8199,U+819e-819f,U+81a1-81a2,U+81a4-81a5,U+81a7,U+81ab;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_16a8ba.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+7cab-7cad,U+7caf-7cb0,U+7cb4,U+7cb6-7cb8,U+7cba-7cbb,U+7cbf-7cc0,U+7cc2-7cc4,U+7cc6,U+7ccb,U+7cce-7cd4,U+7cd8,U+7cda-7cdb,U+7ce1-7ce6,U+7ce9-7cee,U+7cf0-7cf1,U+7cf3,U+7cf5,U+7cf7,U+7cfa,U+7cfc-7cfd,U+7cff,U+7d01,U+7d03,U+7d0c,U+7d0e-7d0f,U+7d11-7d12,U+7d16,U+7d18,U+7d1d-7d1f,U+7d23-7d26,U+7d28-7d2a,U+7d2c-7d2d,U+7d34-7d38,U+7d3b,U+7d3d-7d3e,U+7d41,U+7d45,U+7d47-7d4d,U+7d4f,U+7d51-7d54,U+7d56-7d5a,U+7d5c,U+7d5f-7d60,U+7d63-7d65,U+7d67,U+7d69-7d6d,U+7d6f-7d70,U+7d74-7d76,U+7d78,U+7d7a-7d80,U+7d82,U+7d84-7d85,U+7d87,U+7d89-7d8e,U+7d90,U+7d92,U+7d94-7d9b,U+7d9d,U+7d9f,U+7da1,U+7da4-7da5,U+7da7-7daa,U+7daf,U+7db3,U+7db6-7db7,U+7dbc,U+7dc0-7dc3,U+7dc5-7dc6,U+7dc8-7dc9,U+7dcc-7dd1,U+7dd3-7dd6,U+7dda-7ddc,U+7ddf,U+7de2,U+7de4-7de5,U+7de7,U+7dea-7deb,U+7ded-7dee,U+7df0,U+7df3,U+7df5,U+7df7-7df8,U+7dfa,U+7dfc-7e07,U+7e0c-7e0f,U+7e12-7e1a,U+7e1c,U+7e20-7e22,U+7e24-7e2a,U+7e2c,U+7e2f-7e30,U+7e33-7e34,U+7e38,U+7e3a,U+7e3c,U+7e3f-7e40,U+7e42,U+7e44,U+7e49-7e51,U+7e53,U+7e56-7e59,U+7e5b-7e5d,U+7e5f-7e60,U+7e63-7e68,U+7e6b-7e6c,U+7e71-7e72,U+7e74-7e78,U+7e7a-7e7b,U+7e7f-7e81,U+7e83-7e87,U+7e89;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_2fefd6.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+7ab5,U+7ab7-7ab9,U+7abb-7abe,U+7ac0-7ac3,U+7ac6,U+7ac9,U+7acc-7ad5,U+7ad7-7ad8,U+7ada-7add,U+7ae1-7ae2,U+7ae4,U+7ae7-7aec,U+7aee,U+7af0-7af5,U+7af7-7af8,U+7afb-7afc,U+7afe,U+7b00-7b02,U+7b05,U+7b07,U+7b09,U+7b0c-7b0e,U+7b10,U+7b12-7b13,U+7b16-7b18,U+7b1a,U+7b1c-7b1d,U+7b1f,U+7b21-7b23,U+7b27,U+7b29,U+7b2d,U+7b2f-7b30,U+7b32,U+7b34-7b37,U+7b39,U+7b3b,U+7b3d,U+7b3f-7b44,U+7b48,U+7b4a,U+7b4e,U+7b53,U+7b55,U+7b57,U+7b59,U+7b5c,U+7b5e-7b5f,U+7b61,U+7b63-7b66,U+7b68-7b6d,U+7b6f-7b70,U+7b73-7b74,U+7b76,U+7b78,U+7b7a,U+7b7c-7b7d,U+7b7f,U+7b81-7b84,U+7b86-7b8a,U+7b8c,U+7b8e,U+7b91-7b93,U+7b96,U+7b98-7b9b,U+7b9e-7ba0,U+7ba3-7ba5,U+7bae-7bb0,U+7bb2-7bb3,U+7bb5-7bb7,U+7bb9-7bbf,U+7bc2-7bc3,U+7bc5,U+7bc8,U+7bca,U+7bcd-7bd0,U+7bd2,U+7bd4-7bd8,U+7bdb-7bdc,U+7bde-7be0,U+7be2-7be3,U+7be7-7be8,U+7beb-7bed,U+7bef-7bf0,U+7bf2,U+7bf4-7bf6,U+7bf8-7bfb,U+7bfd,U+7bff,U+7c01-7c06,U+7c08-7c0a,U+7c0e,U+7c10,U+7c12-7c15,U+7c17-7c1d,U+7c20,U+7c22,U+7c24-7c25,U+7c28-7c29,U+7c2c-7c36,U+7c39-7c3d,U+7c42,U+7c44-7c4b,U+7c4e-7c5b,U+7c5d-7c5e,U+7c61-7c63,U+7c65-7c68,U+7c6b,U+7c6d,U+7c6f-7c72,U+7c75-7c7a,U+7c7e-7c88,U+7c8a-7c90,U+7c93-7c94,U+7c96,U+7c99-7c9b,U+7ca0-7ca1,U+7ca3,U+7ca6-7ca9;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_c37197.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+7607-7609,U+760e-760f,U+7611-7612,U+7614,U+7616,U+761a,U+761c-761d,U+7623,U+7628,U+762c,U+762e-762f,U+7631-7632,U+7636-7637,U+7639-763a,U+763d,U+7641,U+7644-7645,U+7648,U+764a-764b,U+764e-7651,U+7653,U+7655,U+7657,U+7659-765b,U+765d,U+7660,U+7666,U+7668,U+766a,U+7673-7677,U+7679-767a,U+767f-7680,U+7683,U+7685,U+7689-768a,U+768c-768d,U+768f-7690,U+7692,U+7694-7695,U+7697-7698,U+769b-76a3,U+76a5-76ad,U+76af,U+76b3,U+76b5-76b7,U+76b9,U+76bb-76be,U+76c0-76c1,U+76c4,U+76c7,U+76c9,U+76cb-76cc,U+76d3,U+76d5,U+76d9-76da,U+76dd,U+76e0,U+76e2,U+76e6,U+76e8-76ed,U+76f0,U+76f3,U+76f5-76f7,U+76fa-76fb,U+76fd,U+76ff-7703,U+7705-7706,U+770a,U+770c,U+770e-7718,U+771b-771d,U+7721,U+7723-7724,U+7727,U+772a-772c,U+772e,U+7730-7734,U+7739,U+773b,U+773d-773f,U+7742,U+7744-7746,U+7748-774e,U+7752-7759,U+775d,U+775f-7760,U+7764,U+7767,U+7769-776a,U+776d-7778,U+777a-777c,U+7781-7783,U+7786,U+7788-778b,U+778f-7790,U+7793-7797,U+7799-779d,U+77a1,U+77a3-77a4,U+77a6,U+77a8,U+77ab,U+77ae-77af,U+77b1-77b2,U+77b4,U+77b6-77ba,U+77be,U+77c0-77cc,U+77ce-77d6,U+77d8-77d9,U+77dd-77e1,U+77e4,U+77e6,U+77e8,U+77ea,U+77f0-77f2,U+77f4-77f5,U+77f7,U+77f9-77fc,U+7803-7808,U+780a-780b,U+780e-7810,U+7813,U+7815,U+7819,U+781b,U+781e,U+7820-7822,U+7824,U+7828,U+782a-782b,U+782e-782f,U+7831-7833,U+7835-7836,U+783d,U+783f,U+7841-7842,U+7844,U+7846,U+7848-784b,U+784d,U+784f,U+7851,U+7853-7854,U+7858-785c,U+785e-7863,U+7865-7867,U+7869,U+7870-7876,U+7878-787b,U+787d-7886,U+7888,U+788a-788b,U+788f-7890,U+7892;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_eeb3fd.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+7894-7896,U+7899,U+789d-789e,U+78a0,U+78a2,U+78a4,U+78a6,U+78a8,U+78aa-78ac,U+78ae-78af,U+78b5-78b7,U+78bb,U+78bd,U+78bf-78c0,U+78c2-78c4,U+78c6-78c8,U+78cc-78cf,U+78d1-78d3,U+78d6-78d8,U+78db-78e2,U+78e4-78e6,U+78e9-78eb,U+78ed-78ee,U+78f0-78f1,U+78f3,U+78f5-78f6,U+78f8-78f9,U+78fb-78fc,U+78fe-7900,U+7902-7904,U+7907-790d,U+790f-7912,U+7914-7918,U+791a-791d,U+7920-7923,U+7925,U+7927-7929,U+792d-7930,U+7932-7933,U+7935-7939,U+793d,U+793f,U+7942-7945,U+7947,U+794a-7952,U+7954,U+7958-7959,U+7961,U+7963-7964,U+7966,U+7969-796c,U+796e,U+7970-7976,U+7979,U+797b-797e,U+7982-7983,U+7986-7989,U+798b-798c,U+7990-7999,U+799b-79a5,U+79a8-79a9,U+79ab-79ad,U+79af,U+79b2,U+79b4-79b8,U+79bc,U+79c2,U+79c4-79c5,U+79c7,U+79ca,U+79cc,U+79ce-79d0,U+79d3-79d4,U+79d6-79d7,U+79d9-79de,U+79e0-79e2,U+79e5,U+79e8,U+79ea,U+79ec,U+79ee,U+79f1-79f7,U+79f9-79fa,U+79fc,U+79fe-79ff,U+7a01,U+7a04,U+7a07,U+7a09-7a0a,U+7a0c,U+7a0f-7a13,U+7a15-7a16,U+7a18-7a19,U+7a1b,U+7a1d,U+7a21-7a22,U+7a24-7a2d,U+7a2f-7a30,U+7a32,U+7a34-7a36,U+7a38,U+7a3a,U+7a3e,U+7a41-7a45,U+7a47-7a4b,U+7a4f-7a50,U+7a52-7a56,U+7a58-7a60,U+7a63-7a68,U+7a6a-7a6c,U+7a6e-7a6f,U+7a71-7a73,U+7a75,U+7a7b-7a7e,U+7a82,U+7a85,U+7a87,U+7a89-7a8c,U+7a8e-7a90,U+7a93-7a94,U+7a99-7a9b,U+7a9e,U+7aa1-7aa4,U+7aa7,U+7aab,U+7ab0-7ab2,U+7ab4;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_76f2de.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+739e,U+73a0-73a1,U+73a3-73a7,U+73aa,U+73ac-73ad,U+73b1,U+73b4-73b6,U+73b8-73b9,U+73bc-73bf,U+73c1,U+73c3-73c7,U+73cb-73cc,U+73ce,U+73d2-73d8,U+73da-73dd,U+73df,U+73e1-73e4,U+73e6,U+73e8,U+73ea-73ec,U+73ee-73f1,U+73f3-73fd,U+73ff-7402,U+7404,U+7407-7408,U+740b-740e,U+7411-7419,U+741c-7421,U+7423-7424,U+7427,U+7429,U+742b,U+742d,U+742f,U+7431-7432,U+7437-7439,U+743b,U+743d-743e,U+7440,U+7442-7448,U+744a,U+744c-7454,U+7456,U+7458,U+745d,U+7460-7462,U+7465-7468,U+746b-746c,U+746e,U+7471-7475,U+7478-747d,U+747f,U+7482,U+7484-7486,U+7488,U+748a,U+748c-748d,U+748f,U+7491-749b,U+749d,U+749f-74a2,U+74a4-74a5,U+74aa-74af,U+74b1-74b9,U+74bb-74bc,U+74be,U+74c0-74c9,U+74cb-74ce,U+74d0-74d1,U+74d3,U+74d5-74d9,U+74db,U+74dd,U+74df,U+74e1,U+74e5,U+74e7-74ed,U+74f0-74f3,U+74f5,U+74f8-74fe,U+7500,U+7502-7503,U+7505-750b,U+750e,U+7510,U+7512,U+7514,U+7516-7517,U+751b,U+751d-751e,U+7520-7521,U+7523-7524,U+7526-7527,U+752a,U+752e,U+7534,U+7536,U+7539,U+753c-753d,U+753f,U+7541-7544,U+7546-7547,U+7549-754a,U+754d,U+7550-7553,U+7555-7558,U+755e-7561,U+7563-7564,U+7567-7569,U+756c-756f,U+7571,U+7573,U+7575,U+7577,U+757a-757e,U+7580-7582,U+7584-7585,U+7588-7589,U+758c-758e,U+7590,U+7593,U+7595,U+7598,U+759b-759c,U+759e,U+75a2,U+75a6-75aa,U+75ad,U+75b6-75b7,U+75ba-75bb,U+75bf-75c1,U+75c6,U+75cb-75cc,U+75ce-75d1,U+75d3,U+75d7,U+75da,U+75dc-75dd,U+75df,U+75e1,U+75e5,U+75e9,U+75ec-75ef,U+75f2-75f3,U+75f5-75f8,U+75fa-75fb,U+75fd,U+7604,U+7606;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_666915.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+716a-716b,U+716d,U+716f-7171,U+7174-7177,U+7179,U+717b-717c,U+717e-7183,U+7185-7189,U+718b-718e,U+7190-7191,U+7193,U+7195-7196,U+719a-719e,U+71a1-71a7,U+71a9-71ab,U+71ad-71b0,U+71b2,U+71b4,U+71b6-71b8,U+71ba-71bd,U+71bf-71c0,U+71c2,U+71c4-71c7,U+71ca-71cd,U+71cf-71d1,U+71d3,U+71d6-71d8,U+71da-71db,U+71dd-71de,U+71e1-71e4,U+71e8-71ec,U+71ef-71f3,U+71f5-71f8,U+71fa,U+71fd,U+71ff-7205,U+7207-720c,U+720e-720f,U+7211-721a,U+721c,U+721e-7227,U+7229,U+722b,U+722e-722f,U+7233-7234,U+723c,U+7241-7245,U+7249-724b,U+724e-7251,U+7253-7255,U+7257,U+725a,U+725c,U+725e,U+7263-7265,U+7268,U+726a-726d,U+7270-7271,U+7273,U+7276-7278,U+727b-727c,U+7282-7283,U+7285-7289,U+728c,U+728e,U+7290-7291,U+7293-7295,U+7297-729a,U+729c-729e,U+72a0-72a1,U+72a3-72a6,U+72a8-72ab,U+72ae,U+72b1-72b3,U+72b5,U+72ba-72bf,U+72c5-72c7,U+72c9-72cc,U+72cf,U+72d1,U+72d3-72d6,U+72d8,U+72da-72dd,U+72df,U+72e2-72e7,U+72ea-72eb,U+72f5-72f6,U+72fe-7300,U+7302,U+7304-7309,U+730b-730d,U+730f-7312,U+7314,U+7318,U+731a,U+731f-7320,U+7323-7324,U+7326-7328,U+732d,U+732f-7330,U+7332-7333,U+7335,U+733a,U+733c-733d,U+7340,U+7342,U+7346-734c,U+734f,U+7351,U+7353-7356,U+7358-735f,U+7361-7367,U+7369,U+736e,U+7371,U+7373-7374,U+7376,U+7379,U+737d,U+737f,U+7381-7383,U+7385-7386,U+7388,U+738a,U+738c-738d,U+738f-7390,U+7392-7395,U+7397-739a,U+739c-739d;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_6938ca.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+6f6a-6f6c,U+6f71,U+6f73,U+6f75-6f76,U+6f79,U+6f7b,U+6f7d-6f7e,U+6f81-6f83,U+6f85,U+6f8a-6f8b,U+6f8f-6f96,U+6f98-6f9b,U+6f9d-6f9f,U+6fa2-6fa3,U+6fa5-6fa6,U+6fa8,U+6faa-6fad,U+6faf-6fb0,U+6fb2,U+6fb4-6fb5,U+6fb7-6fb8,U+6fba-6fbf,U+6fc4-6fc8,U+6fca-6fd0,U+6fd3-6fd7,U+6fd9-6fdd,U+6fe2-6fe3,U+6fe5-6fea,U+6fec-6fed,U+6ff2-6ff9,U+6ffb,U+6ffd,U+6fff-7004,U+7007-7008,U+700a,U+700c-700e,U+7010,U+7012-7014,U+7016-7017,U+7019,U+701c,U+701e,U+7021-7022,U+7024-7025,U+7029-7031,U+7033-7034,U+7036-7038,U+703a-703d,U+703f-7042,U+7045-704b,U+704d-704e,U+7050,U+7052-7054,U+7056-7057,U+7059-705c,U+705f-7062,U+7065-7068,U+706a,U+706e,U+7071-7074,U+7077,U+7079-707b,U+7081-7084,U+7086-7088,U+708b-708d,U+708f-7091,U+7093,U+7097-7098,U+709a-709b,U+709e-70aa,U+70b0,U+70b2,U+70b4-70b6,U+70ba,U+70be-70bf,U+70c4-70c7,U+70c9,U+70cb-70ce,U+70d0-70d7,U+70da,U+70dc-70de,U+70e0-70e3,U+70e5,U+70ea,U+70ee,U+70f0-70f3,U+70f5-70f6,U+70f8,U+70fa-70fc,U+70fe-7108,U+710b-710f,U+7111-7112,U+7114,U+7117,U+711b-7120,U+7122-7125,U+7127-712e,U+7132-7135,U+7137-7144,U+7147-7148,U+714b,U+714d,U+714f-7151,U+7153-7158,U+715a-715b,U+715d,U+715f-7161,U+7163;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_cf29b0.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+6c45,U+6c48,U+6c4b-6c4f,U+6c51-6c53,U+6c56,U+6c58,U+6c5a,U+6c62-6c63,U+6c65-6c67,U+6c6b-6c6f,U+6c71,U+6c73,U+6c75,U+6c77-6c78,U+6c7b-6c7c,U+6c7f-6c80,U+6c84,U+6c87,U+6c8a-6c8b,U+6c8d-6c8e,U+6c91,U+6c95-6c98,U+6c9a,U+6c9c-6c9e,U+6ca0,U+6ca2,U+6ca8,U+6cac,U+6caf-6cb0,U+6cb4-6cb7,U+6cba,U+6cc0,U+6cc2-6cc3,U+6cc6-6cc8,U+6ccb,U+6ccd-6ccf,U+6cd1-6cd2,U+6cd8-6cda,U+6cdc,U+6cdf,U+6ce4,U+6ce6-6ce7,U+6ce9,U+6cec-6ced,U+6cf2,U+6cf4,U+6cf9,U+6cff-6d00,U+6d02-6d03,U+6d05-6d06,U+6d08-6d0a,U+6d0d,U+6d0f-6d11,U+6d13-6d16,U+6d18,U+6d1c-6d1d,U+6d1f-6d24,U+6d26,U+6d28,U+6d2c-6d2d,U+6d2f-6d30,U+6d34,U+6d37-6d38,U+6d3a,U+6d3f-6d40,U+6d42,U+6d44,U+6d49,U+6d4c,U+6d50,U+6d55-6d58,U+6d5b,U+6d5d,U+6d5f,U+6d61-6d62,U+6d64-6d65,U+6d67-6d68,U+6d6b-6d6d,U+6d70-6d73,U+6d75-6d76,U+6d7a-6d7b,U+6d7d-6d81,U+6d83-6d84,U+6d86,U+6d8a-6d8b,U+6d8d,U+6d8f-6d90,U+6d92,U+6d96-6d9a,U+6d9c,U+6da2,U+6da5,U+6dac-6dad,U+6db0-6db1,U+6db3-6db4,U+6db6-6db7,U+6db9-6dbb,U+6dbd-6dbe,U+6dc1-6dc3,U+6dc8-6dca,U+6dcd-6dd0,U+6dd2-6dd5,U+6dd7,U+6ddb-6ddc,U+6ddf,U+6de2-6de3,U+6de7,U+6ded,U+6def-6df0,U+6df2,U+6df4,U+6df8,U+6dfd-6e04,U+6e06-6e08,U+6e0b,U+6e0f,U+6e12-6e13,U+6e15,U+6e18,U+6e1c,U+6e1e-6e1f,U+6e22,U+6e27-6e28,U+6e2a,U+6e2e,U+6e30-6e31,U+6e33,U+6e35-6e37,U+6e39,U+6e3b-6e3d,U+6e3f-6e42,U+6e45-6e49,U+6e4b-6e4c,U+6e4f-6e52,U+6e55,U+6e57,U+6e59-6e5a,U+6e5c-6e5d,U+6e60-6e62,U+6e64-6e66,U+6e68-6e6a,U+6e6c-6e6d,U+6e70-6e7d,U+6e80-6e82,U+6e84,U+6e87-6e88,U+6e8a-6e8e,U+6e91-6e95,U+6e97,U+6e99-6e9b,U+6e9e,U+6ea0-6ea1,U+6ea3-6ea4,U+6ea6,U+6ea8-6ea9,U+6eac-6eae,U+6eb0,U+6eb3,U+6eb5,U+6eb8-6eb9,U+6ebe-6ec0,U+6ec3,U+6ec6,U+6ec8-6eca,U+6ecd,U+6ed0,U+6ed2,U+6ed6,U+6ed8-6ed9,U+6edb-6edd,U+6ee3,U+6ee7,U+6eea-6eeb,U+6eed-6eee,U+6ef0-6ef1,U+6ef3,U+6ef5-6ef6,U+6efa-6efd,U+6f00,U+6f03-6f05,U+6f07-6f08,U+6f0a-6f0e,U+6f10,U+6f12,U+6f16-6f19,U+6f1b-6f1f,U+6f21,U+6f25-6f28,U+6f2e,U+6f30,U+6f34,U+6f37,U+6f39-6f3d,U+6f40,U+6f42-6f45,U+6f48-6f4a,U+6f4c,U+6f4e-6f50,U+6f52-6f53,U+6f55-6f57,U+6f5a,U+6f5d,U+6f5f-6f61,U+6f63,U+6f65,U+6f67-6f69;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_b8977e.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+6a56-6a57,U+6a5a,U+6a5c-6a5e,U+6a60,U+6a63-6a64,U+6a66-6a6a,U+6a6c-6a70,U+6a72-6a78,U+6a7a-6a7b,U+6a7d-6a7f,U+6a82-6a83,U+6a85-6a88,U+6a8a-6a8d,U+6a8f,U+6a92-6a93,U+6a95-6a96,U+6a98-6a9b,U+6a9d-6a9f,U+6aa1,U+6aa4-6aa8,U+6aaa,U+6aad-6ab2,U+6ab4-6ab7,U+6ab9-6aba,U+6abc-6abd,U+6abf-6ac2,U+6ac4-6ad2,U+6ad4-6ad9,U+6adc,U+6ae0-6ae4,U+6ae6,U+6ae9,U+6aed-6af0,U+6af2,U+6af4-6af7,U+6af9-6afa,U+6afc-6b03,U+6b05-6b09,U+6b0b-6b0e,U+6b10-6b11,U+6b13-6b15,U+6b17-6b1d,U+6b1f,U+6b25-6b26,U+6b28-6b31,U+6b33-6b36,U+6b38,U+6b3b-6b3c,U+6b3f-6b42,U+6b44-6b45,U+6b48,U+6b4a-6b4b,U+6b4d-6b4f,U+6b51-6b58,U+6b5a-6b5e,U+6b60,U+6b68-6b69,U+6b6b-6b71,U+6b73-6b76,U+6b7a,U+6b7d-6b7e,U+6b80,U+6b85,U+6b88,U+6b8c,U+6b8e-6b91,U+6b94-6b95,U+6b97,U+6b99,U+6b9c-6b9d,U+6b9f-6ba0,U+6ba2-6ba3,U+6ba5-6ba9,U+6bac,U+6bb0-6bb1,U+6bb6,U+6bb8-6bb9,U+6bbb,U+6bbd-6bbe,U+6bc3-6bc4,U+6bc7-6bca,U+6bcc,U+6bce,U+6bd0-6bd1,U+6bd8,U+6bda,U+6bdc-6be0,U+6be2-6be9,U+6bec-6bee,U+6bf0-6bf2,U+6bf4,U+6bf6-6bf8,U+6bfa-6bfc,U+6bfe,U+6c00-6c04,U+6c09-6c0b,U+6c0e,U+6c12,U+6c17,U+6c1c-6c1e,U+6c20,U+6c25,U+6c2d,U+6c31,U+6c36-6c37,U+6c3a-6c3c,U+6c3e-6c3f,U+6c43-6c44;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_8ea677.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+681e-6820,U+6822-6828,U+682b-6831,U+6834-6836,U+683a-683b,U+683f,U+6847,U+684b,U+684d,U+684f,U+6852,U+6856-685f,U+686a,U+686c-6873,U+6875,U+6878-687e,U+6880,U+6882,U+6884,U+6887-688e,U+6890-6892,U+6895-6896,U+6899-689c,U+689e,U+68a0-68a1,U+68a3-68a5,U+68a9-68ac,U+68ae,U+68b1-68b2,U+68b4,U+68b6-68bf,U+68c1,U+68c3,U+68c5-68c8,U+68cc,U+68ce-68d1,U+68d3-68d4,U+68d9,U+68db-68de,U+68e1-68e2,U+68e4-68e6,U+68e8-68eb,U+68ed,U+68ef,U+68f3-68f4,U+68f6-68f8,U+68fb,U+68fd-6900,U+6902-6904,U+6906-690a,U+690c,U+6911,U+6913-691e,U+6921-6923,U+6925-692c,U+692e-692f,U+6931-6933,U+6935-6938,U+693a-693c,U+693e,U+6940-6941,U+6943-6949,U+694b-6952,U+6955-6956,U+6958-6959,U+695b-695c,U+695f,U+6961-6962,U+6964-6965,U+6967,U+6969-696a,U+696c,U+696f-6970,U+6972-6974,U+6976,U+697a-697b,U+697d-697f,U+6981,U+6983,U+6985,U+698a-698c,U+698e-6993,U+6996-6997,U+6999-699a,U+699d-69a6,U+69a9,U+69ac,U+69af-69b0,U+69b2-69b3,U+69b5-69b6,U+69b8-69ba,U+69bc-69be,U+69c0,U+69c2-69c9,U+69cf,U+69d1-69d2,U+69d5-69da,U+69dc-69de,U+69e1-69e6,U+69e9,U+69eb-69ec,U+69ee-69f1,U+69f4-69fc,U+69fe,U+6a00,U+6a03-6a04,U+6a06-6a09,U+6a0b-6a10,U+6a12,U+6a14-6a16,U+6a1a-6a1d,U+6a20,U+6a22,U+6a24-6a27,U+6a29,U+6a2b-6a2e,U+6a30,U+6a32-6a33,U+6a36-6a37,U+6a3b-6a3c,U+6a3f-6a43,U+6a45-6a46,U+6a49-6a4a,U+6a4c-6a4f,U+6a51-6a55;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_0d19f4.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+653a,U+653c-653d,U+6540-6544,U+6546-6547,U+654a-654b,U+654d,U+6550,U+6552-6554,U+655a,U+655c,U+655f-6561,U+6564-6565,U+6567-656a,U+656d-656f,U+6571,U+6573,U+6576,U+6579-6581,U+6584-6586,U+6588-658a,U+658d-658f,U+6592,U+6594,U+6596,U+6598,U+659a,U+659d-659e,U+65a0,U+65a2-65a3,U+65a6,U+65a8,U+65aa,U+65ae,U+65b1-65b6,U+65b8,U+65ba-65bb,U+65be-65c0,U+65c7-65ca,U+65cd,U+65d0-65d1,U+65d3-65d5,U+65d8-65df,U+65e1,U+65e4,U+65ea-65eb,U+65f2-65f5,U+65f8-65f9,U+65fb-65ff,U+6601,U+6604-6605,U+6607-6609,U+660b,U+660d,U+6610-6612,U+6616-6618,U+661a-661c,U+661e,U+6621-6624,U+6626,U+6629-662c,U+662e,U+6630,U+6632-6633,U+6637-663b,U+663d,U+663f-6640,U+6644-6648,U+664a,U+664d-664e,U+6650-6651,U+6658-6659,U+665b-665c,U+665e,U+6660,U+6662-6663,U+6665,U+6667,U+6669-666d,U+6671-6673,U+6675,U+6678-6679,U+667b-667d,U+667f-6681,U+6683,U+6685-6686,U+668a-668b,U+668d-6690,U+6692-6695,U+6698-669c,U+669e-66a1,U+66a3-66a6,U+66a9-66aa,U+66ac-66ad,U+66af-66b0,U+66b2-66b3,U+66b5-66b8,U+66ba-66bd,U+66bf-66c3,U+66c5-66c6,U+66c8,U+66ca-66d5,U+66d7-66d8,U+66da,U+66de-66df,U+66e1-66e5,U+66e7-66e8,U+66ea-66eb,U+66ed-66ef,U+66f1,U+66f5-66f6,U+66fa-66fb,U+66fd,U+6701-6702,U+6704-6707,U+670c,U+670e-670f,U+6711-6713,U+6716,U+6718-671a,U+671c,U+671e,U+6720-6725,U+6729,U+672e,U+6730,U+6732-6733,U+6737-6739,U+673b-673c,U+673e-673f,U+6741,U+6744-6745,U+6747,U+674a-674b,U+674d,U+6752,U+6754-6755,U+6757-675b,U+675d,U+6762-6764,U+6766-6767,U+676b-676c,U+676e,U+6774,U+6776,U+6778-677b,U+677d,U+6780,U+6782-6783,U+6785-6786,U+6788,U+678a,U+678c-678f,U+6791-6794,U+6796,U+6799,U+679b,U+679f-67a1,U+67a4,U+67a6,U+67a9,U+67ac,U+67ae,U+67b1-67b2,U+67b4,U+67b9-67c0,U+67c2,U+67c5-67ce,U+67d5-67d7,U+67db,U+67df,U+67e1,U+67e3-67e4,U+67e6-67e8,U+67ea-67eb,U+67ed-67ee,U+67f2,U+67f6-67fa,U+67fc,U+67fe,U+6801-6804,U+6806,U+680d,U+6810,U+6812,U+6814-6815,U+6818-681c;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_87c494.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+6235,U+6238-623c,U+6242,U+6244-6246,U+624a,U+624f-6250,U+6255-6257,U+6259-625a,U+625c-6260,U+6262,U+6264-6265,U+6268,U+6271-6272,U+6274-6275,U+6277-6278,U+627a-627b,U+627d,U+6281-6283,U+6285-6288,U+628b-6290,U+6294,U+6299,U+629c-629e,U+62a3,U+62a6-62a7,U+62a9-62aa,U+62ad-62b0,U+62b2-62b4,U+62b6-62b8,U+62ba,U+62be,U+62c0-62c1,U+62c3,U+62cf,U+62d1,U+62d5,U+62de,U+62e0-62e1,U+62e4,U+62ea-62eb,U+62f0,U+62f2,U+62f5,U+62f8-62fb,U+6300,U+6303-6306,U+630a-630d,U+630f-6310,U+6312-6315,U+6317-6319,U+631c,U+6326-6327,U+6329,U+632c-632e,U+6330,U+6333-6338,U+633b-633c,U+633f-6341,U+6344,U+6347-6348,U+634a,U+6351-6354,U+6356-635d,U+6360,U+6364-6366,U+636a,U+636c,U+636f-6370,U+6373-6375,U+6378-6379,U+637c-637f,U+6381,U+6385-6386,U+638b,U+638d,U+6391,U+6393-6395,U+6397,U+639a,U+639c-639f,U+63a1,U+63a4,U+63a6,U+63ab,U+63af,U+63b1-63b2,U+63b5-63b6,U+63b9,U+63bb,U+63bd,U+63bf,U+63c1-63c3,U+63c5,U+63c7-63c8,U+63ca-63cc,U+63d1,U+63d3-63d5,U+63d7-63d9,U+63dc-63dd,U+63df,U+63e2,U+63e4-63e8,U+63eb-63ec,U+63ef-63f1,U+63f3,U+63f5,U+63f7,U+63fa-63fc,U+63fe,U+6403-6404,U+6406-640a,U+640e,U+6411-6412,U+6415,U+6418-641a,U+641d,U+641f,U+6422-6424,U+6427-6429,U+642b,U+642e-6433,U+6435,U+6437-6439,U+643b-643c,U+643e,U+6442-6443,U+6449,U+644b-6450,U+6453,U+6455-6457,U+6459-645b,U+645d,U+6460-6462,U+6464-6466,U+6468,U+646a-646c,U+646e,U+6470-6472,U+6474-6475,U+6477,U+647c-6481,U+6483,U+6486,U+6489-648f,U+6494,U+6497-6498,U+649b-649d,U+64a0-64a2,U+64a6-64a8,U+64aa,U+64af,U+64b1,U+64b4,U+64b6,U+64b9,U+64bd,U+64c3,U+64c6,U+64c8-64c9,U+64cc,U+64cf,U+64d1,U+64d3,U+64d5-64d6,U+64d9,U+64db-64dd,U+64df,U+64e3,U+64e5,U+64e7-64eb,U+64ed-64ee,U+64f3,U+64f5-64f6,U+64f8-64f9,U+64fd,U+64ff,U+6501-6503,U+6505,U+6507-6508,U+650a-650e,U+6510-6511,U+6513,U+6515,U+6517,U+651a,U+651e-6521,U+6526-6529,U+652d,U+6530-6533,U+6537;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_3b743f.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+5f60,U+5f63,U+5f67-5f68,U+5f6b,U+5f6e-5f6f,U+5f72,U+5f74-5f76,U+5f78,U+5f7a,U+5f7d-5f7e,U+5f83,U+5f86,U+5f8d-5f8f,U+5f93-5f94,U+5f96,U+5f9a-5f9b,U+5f9d,U+5f9f,U+5fa2-5fa7,U+5fab-5fac,U+5faf-5fb4,U+5fb6,U+5fb8,U+5fba-5fbb,U+5fbe-5fc2,U+5fc7-5fc8,U+5fca-5fcb,U+5fce,U+5fd3-5fd5,U+5fda-5fdc,U+5fde-5fdf,U+5fe2-5fe3,U+5fe5-5fe6,U+5fe8-5fe9,U+5fec,U+5fef-5ff0,U+5ff2-5ff4,U+5ff6-5ff7,U+5ff9-5ffa,U+5ffc,U+6007-6009,U+600b-600c,U+6010-6011,U+6013,U+6017-6018,U+601a,U+601e-601f,U+6022-6024,U+602c-602e,U+6030-6034,U+6036-603a,U+603d-603e,U+6040,U+6044-6045,U+6047-604a,U+604c,U+604e-604f,U+6051,U+6053-6054,U+6056-6058,U+605b-605c,U+605e-6061,U+6066,U+606e,U+6071-6072,U+6074-6075,U+6077,U+607e,U+6080-6082,U+6086-6088,U+608a-608b,U+608e-6091,U+6093,U+6095,U+6097-6099,U+609c,U+609e,U+60a1-60a2,U+60a4-60a5,U+60a7,U+60a9-60aa,U+60ae,U+60b0,U+60b3,U+60b7,U+60b9-60ba,U+60be-60c4,U+60c7-60c9,U+60cc-60d0,U+60d2-60d4,U+60d6-60d7,U+60d9,U+60db,U+60de,U+60e2-60e5,U+60ea,U+60f5,U+60f7-60f8,U+60fc-60ff,U+6102-6105,U+6107,U+610a-610c,U+6110-6114,U+6116-6119,U+611d-611e,U+6121-6122,U+6125,U+6129-612a,U+612c-6133,U+6135-6136,U+6138-613d,U+6140-6141,U+6143,U+6145-6147,U+6149,U+614f-6150,U+6152-6154,U+6156-6157,U+6159,U+615b-615c,U+615e,U+6160-6161,U+6164-6166,U+6169,U+616c-616d,U+616f,U+6171-6172,U+6174,U+6178-617b,U+617d,U+617f-6181,U+6183-6189,U+618c-618d,U+618f,U+6193,U+6195-6199,U+619b-619c,U+619e-61a3,U+61a5-61a6,U+61aa,U+61ad,U+61af-61b1,U+61b3-61b5,U+61b8-61bd,U+61bf-61c1,U+61c3-61c6,U+61ce-61d0,U+61d3,U+61d5-61de,U+61e0-61e2,U+61e4-61e5,U+61e7,U+61e9-61f1,U+61f3-61f4,U+61f9,U+61fb,U+61fd,U+6201-6205,U+6209,U+6213,U+6219,U+621c-621e,U+6220,U+6223,U+6226,U+6228,U+622b,U+622d,U+622f,U+6231;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_5825df.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+5cde-5ce0,U+5ce2-5ce3,U+5ce7,U+5ceb-5cec,U+5cee,U+5cf1-5cf3,U+5cf5,U+5cf7-5cfa,U+5cfc,U+5cfe-5d01,U+5d04-5d05,U+5d08-5d0c,U+5d0f-5d10,U+5d12-5d13,U+5d15,U+5d18-5d1a,U+5d1c-5d1d,U+5d1f-5d21,U+5d23,U+5d25,U+5d28,U+5d2a-5d2b,U+5d2f-5d32,U+5d35-5d3c,U+5d3f-5d46,U+5d48-5d49,U+5d4d-5d4f,U+5d51-5d57,U+5d59-5d5a,U+5d5c,U+5d5e-5d68,U+5d6a,U+5d6d-5d6e,U+5d70-5d73,U+5d75-5d80,U+5d83,U+5d85-5d86,U+5d88-5d96,U+5d98,U+5d9a-5d9c,U+5d9e-5d9f,U+5da1-5da6,U+5da8-5db3,U+5db5-5db6,U+5db9,U+5dbb,U+5dbe-5dc4,U+5dc6-5dca,U+5dcc,U+5dce-5dd1,U+5dd3,U+5dd5,U+5dd7-5dda,U+5ddc,U+5ddf-5de0,U+5de4,U+5dea,U+5dec-5ded,U+5df5-5df6,U+5df8,U+5dfa-5dfc,U+5dff-5e00,U+5e04,U+5e07,U+5e09-5e0b,U+5e0d-5e0e,U+5e12-5e13,U+5e17,U+5e1e-5e24,U+5e28-5e2a,U+5e2c,U+5e2f-5e30,U+5e32,U+5e34-5e35,U+5e39-5e3a,U+5e3e-5e3f,U+5e41,U+5e46-5e4b,U+5e4d-5e53,U+5e56,U+5e59-5e5a,U+5e5c-5e5d,U+5e60,U+5e64-5e6a,U+5e6d-5e71,U+5e75,U+5e77,U+5e81-5e83,U+5e85,U+5e88-5e89,U+5e8c-5e8e,U+5e92,U+5e98,U+5e9b,U+5e9d,U+5ea1-5ea4,U+5ea8-5eaa,U+5eac,U+5eae-5eb2,U+5eb4,U+5eba-5ebd,U+5ebf-5ec0,U+5ec3,U+5ec5-5ec7,U+5ecb-5ed0,U+5ed4-5ed5,U+5ed7-5ed9,U+5edc,U+5ede,U+5ee0,U+5ee4-5ee7,U+5eeb,U+5eed-5ef2,U+5ef5,U+5ef8-5ef9,U+5efb-5efd,U+5f05-5f07,U+5f09,U+5f0c-5f0e,U+5f10,U+5f16,U+5f19-5f1a,U+5f1c-5f1e,U+5f21-5f24,U+5f28,U+5f2b-5f2c,U+5f2e,U+5f30,U+5f32,U+5f34,U+5f36,U+5f38,U+5f3b,U+5f3d-5f3f,U+5f41-5f47,U+5f49-5f4b,U+5f4d,U+5f4f,U+5f51,U+5f54,U+5f59-5f5c,U+5f5f;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_1c96d4.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+5a4b-5a54,U+5a56-5a59,U+5a5b-5a61,U+5a63-5a65,U+5a68-5a69,U+5a6b-5a6c,U+5a6e-5a73,U+5a78-5a79,U+5a7b-5a7e,U+5a80-5a91,U+5a93-5a99,U+5a9c-5aa6,U+5aa8-5aa9,U+5aab-5ab1,U+5ab4,U+5ab6-5ab7,U+5ab9-5abb,U+5abf-5ac0,U+5ac3-5ac8,U+5aca,U+5acd-5ad1,U+5ad3,U+5ad5,U+5ad9-5adb,U+5add-5adf,U+5ae2,U+5ae4-5ae5,U+5ae7-5ae8,U+5aea,U+5aec-5af0,U+5af2-5af4,U+5af6-5af9,U+5afb-5aff,U+5b01-5b07,U+5b0a,U+5b0d-5b15,U+5b18,U+5b1a-5b20,U+5b22-5b23,U+5b25-5b29,U+5b2b-5b2f,U+5b31,U+5b33,U+5b35-5b36,U+5b39-5b3f,U+5b41-5b42,U+5b44-5b4b,U+5b4d-5b4f,U+5b52,U+5b56,U+5b5e,U+5b60-5b61,U+5b67-5b68,U+5b6d-5b6f,U+5b72,U+5b74,U+5b77,U+5b79,U+5b7b-5b7c,U+5b7e,U+5b82,U+5b86,U+5b8a,U+5b8d-5b8e,U+5b90-5b92,U+5b94,U+5b96,U+5b9f,U+5ba7-5ba9,U+5bac-5bad,U+5baf,U+5bb1-5bb2,U+5bb7,U+5bba-5bbc,U+5bc0-5bc1,U+5bc3,U+5bc8-5bcb,U+5bcd-5bcf,U+5bd1,U+5bd4-5bdc,U+5be0,U+5be3,U+5bea,U+5bed,U+5bef,U+5bf1-5bf4,U+5bf7,U+5bfd-5bfe,U+5c00,U+5c02-5c03,U+5c05,U+5c0c,U+5c10,U+5c12-5c13,U+5c17,U+5c19,U+5c1b,U+5c1e-5c21,U+5c23,U+5c26,U+5c28-5c2b,U+5c2d-5c30,U+5c32-5c33,U+5c35-5c36,U+5c43-5c44,U+5c47,U+5c4c,U+5c52-5c54,U+5c56-5c58,U+5c5a,U+5c5d,U+5c5f,U+5c67,U+5c69-5c6b,U+5c6d,U+5c70,U+5c72-5c78,U+5c7b-5c7e,U+5c80,U+5c83-5c87,U+5c89-5c8b,U+5c8e-5c8f,U+5c92-5c93,U+5c95,U+5c9d-5ca0,U+5ca4-5ca8,U+5caa,U+5cae-5cb0,U+5cb2,U+5cb4,U+5cb6,U+5cb9-5cbc,U+5cbe,U+5cc0,U+5cc2-5cc3,U+5cc5-5cca,U+5ccc-5cd1,U+5cd3-5cd8,U+5cda-5cdd;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_eac724.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+57b9-57c1,U+57c4-57ca,U+57cc-57cd,U+57d0-57d1,U+57d3,U+57d6-57d7,U+57db-57dc,U+57de,U+57e2-57e3,U+57e5-57ec,U+57ee,U+57f0-57f3,U+57f5-57f6,U+57fb-57fc,U+57fe-57ff,U+5801,U+5803-5804,U+5808-5809,U+580c,U+580e-5810,U+5812-5814,U+5817-5818,U+581a-581c,U+581f,U+5822-5823,U+5825-5829,U+582b-582e,U+5832-5833,U+5836-5843,U+5845-5849,U+584e,U+5850,U+5853,U+5855-5857,U+5859,U+585b-585d,U+585f-5861,U+5863-5864,U+5866-586a,U+586d-5874,U+5876-5878,U+587a-587d,U+587f,U+5882,U+5884,U+5886-5888,U+588b-5891,U+5894-5898,U+589b,U+589d,U+58a0-58a7,U+58aa-58ad,U+58af-58b2,U+58b4-58bb,U+58bd,U+58bf-58c0,U+58c2-58c4,U+58c6,U+58c8-58cd,U+58cf-58d0,U+58d2,U+58d4,U+58d6-58d7,U+58db-58dd,U+58e1,U+58e3,U+58e5-58e8,U+58ea,U+58ed,U+58f1-58f2,U+58f4-58f5,U+58f7-58f8,U+58fb-58fc,U+58fe-5901,U+5903,U+5905-5906,U+5908-590c,U+590e,U+5910-5913,U+5917-5918,U+591b,U+591d-591e,U+5921,U+5923,U+5926,U+5928,U+592c,U+5930,U+5932-5933,U+5935-5936,U+593b,U+593d,U+593f-5940,U+5943,U+5945-5946,U+594a,U+594c-594d,U+5952-5953,U+5959,U+595b-595f,U+5961,U+5963-5964,U+5966,U+5968,U+596b-596d,U+596f-5972,U+5975,U+5977,U+597a-597b,U+597e-5980,U+5985,U+5989,U+598b-598c,U+598e-5991,U+5994-5995,U+5998,U+599a-599c,U+599f-59a2,U+59a6-59a7,U+59ac-59ad,U+59b0-59b1,U+59b4-59b8,U+59ba,U+59bc-59bd,U+59bf-59c5,U+59c7-59c9,U+59cc,U+59ce-59cf,U+59d5-59d6,U+59d9,U+59db,U+59de-59e2,U+59e4,U+59e7,U+59e9-59eb,U+59ed-59f8,U+59fa,U+59fc-59fe,U+5a00,U+5a02,U+5a0a-5a0b,U+5a0d-5a10,U+5a12,U+5a14-5a17,U+5a19-5a1a,U+5a1d-5a1e,U+5a21-5a22,U+5a24,U+5a26-5a28,U+5a2a-5a30,U+5a33,U+5a35,U+5a37-5a3b,U+5a3d-5a3f,U+5a42-5a45,U+5a47-5a48;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_5ae301.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+50c4,U+50c6-50c8,U+50ca-50ce,U+50d0,U+50d2-50d4,U+50d7-50d9,U+50db-50dd,U+50df-50e4,U+50e9-50eb,U+50ef-50f0,U+50f2,U+50f4,U+50f6-50f8,U+50fa,U+50fc-50ff,U+5103,U+5105,U+510a,U+510c,U+510e-510f,U+5111,U+5113,U+5116-511e,U+5120,U+5122-5129,U+512b-5131,U+5133-5136,U+5138-5139,U+513d-513e,U+5142,U+514a,U+514e-5150,U+5153,U+5158-5159,U+515b,U+515d-5161,U+5163-5164,U+5166,U+516f,U+5172,U+517a,U+517e-517f,U+5183-5184,U+5186-5187,U+518b,U+518e-5190,U+5193-5194,U+5198,U+519a,U+519d-519f,U+51a1,U+51a3,U+51a6-51a9,U+51ad-51ae,U+51b4,U+51b8-51ba,U+51be-51bf,U+51c1-51c3,U+51c5,U+51c8,U+51ca,U+51ce,U+51d0,U+51d2-51da,U+51de-51df,U+51e2-51e3,U+51e5-51ea,U+51ec,U+51ee,U+51f2,U+51f4,U+51f7,U+51fe,U+5204-5205,U+5209,U+520b-520c,U+520f-5210,U+5213-5215,U+521c,U+521e-521f,U+5221-5223,U+5226-5227,U+522c,U+522f,U+5231-5232,U+5234-5235,U+523c,U+523e,U+5245-5246,U+5248-5249,U+524f,U+5252-5253,U+5255,U+5257-525a,U+525f-5260,U+5262-5264,U+5266,U+5268,U+526b-526d,U+5271,U+5273,U+5276-527c,U+527e,U+5280,U+5285-5286,U+528b,U+528e-528f,U+5292,U+5294-529a,U+529c,U+52a4-52a7,U+52ae-52b0,U+52b4-52bd,U+52c0,U+52c2,U+52c4-52c6,U+52c8,U+52ca,U+52cc-52cf,U+52d1,U+52d3-52d4,U+52d7,U+52da-52dc,U+52e0-52e1,U+52e3,U+52e5-52ef,U+52f2,U+52f4,U+52f6-52f7,U+52fc-52fd,U+5301-5304,U+5307,U+5309-530c,U+530e,U+5311-5314,U+5318,U+531b-531c,U+531e-531f,U+5322,U+5324-5325,U+5327-5329,U+532b-532c,U+5330,U+5332-5338,U+533c-533d,U+5342,U+5344,U+5346,U+534b-534d,U+5350,U+5358-5359,U+535b,U+535d,U+5365,U+5368,U+536a,U+536c-536d,U+5372,U+5376,U+537c,U+537e,U+5380-5381,U+5383,U+5387-5388,U+538a,U+538e-5394,U+5396-5397,U+539b-539c,U+539e,U+53a0-53a1,U+53a4,U+53a7,U+53aa-53ac,U+53af-53b1,U+53b3,U+53b5,U+53b7-53ba,U+53bc-53be,U+53c0,U+53c5-53c7,U+53ce-53d0,U+53d2-53d3,U+53d5,U+53da,U+53dc-53de,U+53e1,U+53e7,U+53f4,U+53fa,U+53fe-5400,U+5402,U+5405,U+5407,U+540b,U+5414,U+5418-541a,U+541c,U+5422,U+5424-5425,U+542a,U+5430,U+5437,U+543a,U+543d,U+543f,U+5441,U+5444-5445,U+5447,U+5449,U+544c-544f,U+545a,U+545d-5461,U+5463,U+5465,U+5467,U+5469-546f;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_306bba.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+5470,U+5474,U+5479-547a,U+547e-547f,U+5481,U+5483,U+5485,U+5487-548a,U+548d,U+5491,U+5493,U+5497-5498,U+549c,U+549e-54a2,U+54a5,U+54ae,U+54b0,U+54b2,U+54b5-54b7,U+54b9-54ba,U+54be,U+54c3,U+54c5,U+54ca-54cb,U+54d6,U+54d8,U+54db,U+54e0,U+54e3-54e4,U+54eb-54ec,U+54ef-54f1,U+54f4-54f9,U+54fb,U+54fe,U+5500,U+5502-5503,U+5505,U+5508,U+550a-550e,U+5512-5513,U+5516-5519,U+551c-551f,U+5521,U+5525-5526,U+5528-5529,U+552b,U+552d,U+5532,U+5534-5536,U+5539-553b,U+553d,U+5540,U+5542,U+5545,U+5547-5548,U+554b-554e,U+5551-5552,U+5554,U+5557-555b,U+555d,U+555f-5560,U+5562-5563,U+5568-5569,U+556b,U+556f-5574,U+5579-557a,U+557d,U+557f,U+5585,U+558c-558e,U+5590,U+5592-5593,U+5595-5597,U+559b,U+55a0-55a6,U+55a8-55a9,U+55ad,U+55af-55b0,U+55b4,U+55b6,U+55b8,U+55ba,U+55bc,U+55bf-55c3,U+55c8,U+55ca-55cb,U+55cf-55d0,U+55d5,U+55d7-55d9,U+55db,U+55de,U+55e0,U+55e2,U+55e7,U+55ed-55ee,U+55f0-55f1,U+55f4,U+55f8-55fc,U+55ff,U+5602-5605,U+5607,U+560a-560b,U+5610-5613,U+5615,U+5619-561a,U+561d,U+5620-5622,U+5625-5626,U+5628-562b,U+5633,U+5637,U+563a,U+563c-563e,U+5640-5644,U+5646-564b,U+564f-5652,U+5655-5656,U+565a-565b,U+565e-565f,U+5661,U+5663,U+5667,U+566d-566e,U+5670,U+5673,U+5675,U+5677,U+5679-567a,U+567d-567f,U+5681-5684,U+5688-568b,U+568d,U+5690-5692,U+5696-56a2,U+56a4,U+56a7,U+56a9-56ae,U+56b0-56b2,U+56b5,U+56b8-56bb,U+56bd-56bf,U+56c3-56c4,U+56c6-56c7,U+56c9,U+56cb-56d0,U+56d2-56d3,U+56d5-56d6,U+56d8-56d9,U+56dc,U+56e3,U+56e5-56e9,U+56ec,U+56ee-56ef,U+56f2-56f3,U+56f6-56f8,U+56fb-56fc,U+5700-5702,U+5705,U+570c,U+570e,U+5710-5711,U+5714-5715,U+5717,U+5719-571b,U+571d-571e,U+5720-5722,U+5724-5727,U+572b,U+5731-5732,U+5734-5738,U+573c-573d,U+573f,U+5741,U+5743-5746,U+5748-5749,U+574b,U+5752-5756,U+5758-5759,U+5762-5763,U+5765,U+5767,U+576c,U+576e,U+5771-5772,U+5774-5775,U+5778-577a,U+577d-5781,U+5787-578a,U+578d-5791,U+5794-579a,U+579d-579f,U+57a5,U+57a8,U+57aa,U+57ac,U+57af-57b1,U+57b3,U+57b6-57b7;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_6d65eb.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4d67-4db5,U+4e02,U+4e04-4e06,U+4e0f,U+4e12,U+4e17,U+4e20-4e21,U+4e23,U+4e29,U+4e2e-4e2f,U+4e31,U+4e33,U+4e35,U+4e37,U+4e3c,U+4e40-4e42,U+4e44,U+4e46,U+4e4a,U+4e51,U+4e55,U+4e57,U+4e5a-4e5b,U+4e62-4e65,U+4e67-4e68,U+4e6a-4e6f,U+4e72,U+4e74-4e7d,U+4e7f-4e81,U+4e83-4e85,U+4e87,U+4e8a,U+4e90,U+4e96-4e97,U+4e9c-4e9d,U+4ea3,U+4eaa,U+4eaf-4eb1,U+4eb4,U+4eb6-4eb9,U+4ebc-4ebe,U+4ec8,U+4ecc,U+4ecf-4ed0,U+4ed2,U+4eda-4edc,U+4ee0,U+4ee2,U+4ee6-4ee7,U+4ee9,U+4eed-4eef,U+4ef1,U+4ef4,U+4ef8-4efa,U+4efc,U+4efe,U+4f00,U+4f02-4f08,U+4f0b-4f0c,U+4f12-4f14,U+4f16,U+4f1c-4f1d,U+4f21,U+4f23,U+4f28-4f29,U+4f2c-4f2e,U+4f31,U+4f33,U+4f35,U+4f37,U+4f39,U+4f3b,U+4f3e-4f42,U+4f44-4f45,U+4f49-4f4c,U+4f52,U+4f56,U+4f61-4f62,U+4f66,U+4f68,U+4f6a-4f6b,U+4f6d-4f6e,U+4f71-4f72,U+4f75,U+4f77-4f7a,U+4f7d,U+4f80-4f82,U+4f85,U+4f87,U+4f8a,U+4f8c,U+4f8e,U+4f90,U+4f92-4f93,U+4f95,U+4f98-4f9a,U+4f9c,U+4f9e-4f9f,U+4fa1-4fa2,U+4fa4,U+4fab,U+4fad,U+4fb0-4fb4,U+4fb8-4fbe,U+4fc0,U+4fc2,U+4fc6-4fc9,U+4fcb-4fcd,U+4fd2-4fd6,U+4fd9,U+4fdb,U+4fe2,U+4fe4-4fe5,U+4fe7,U+4feb,U+4ff0,U+4ff2,U+4ff4-4ff7,U+4ff9,U+4ffb-4ffd,U+4fff,U+5001-5005,U+5007-5008,U+500a,U+500e,U+5010,U+5013,U+5015,U+5017,U+501b,U+501d-501e,U+5020,U+5022-5024,U+5027,U+502f-5039,U+503b,U+503d,U+503f-5042,U+5044-5046,U+504a-504b,U+504d,U+5050-5054,U+5056-5059,U+505b,U+505d-5064,U+5066-506b,U+506d-5073,U+5079-507a,U+507c-507d,U+5081-5084,U+5086-5087,U+5089-508c,U+508e-5090,U+5092-5095,U+5097,U+509a-50a1,U+50a4,U+50a6,U+50aa-50ab,U+50ae,U+50b0-50b1,U+50b6,U+50b8-50b9,U+50bc-50bd,U+50bf-50c1,U+50c3,U+30d7c,U+30de5,U+310a8,U+310b1,U+3114b;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_0dbc27.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4c74-4d66;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_135790.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4b68-4c73,U+2f80f;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_813dd1.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4a4f-4b67,U+2e774,U+2e779,U+2e8f4,U+2e932,U+2e9f4-2e9f5,U+2ea5d;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_ed0d20.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4923-4a4e,U+2dc4a,U+2e0c4,U+2e267-2e268,U+2e26a-2e26b,U+2e428,U+2e64b;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_3149e0.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+47d8-4922,U+2ce7c,U+2ce88,U+2ce93,U+2d075,U+2d079,U+2d6a6,U+2d8c7;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_71407d.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+46e9-47d7,U+2c542,U+2c613,U+2c618,U+2c621,U+2c629,U+2c62b-2c62d,U+2c62f,U+2c642,U+2c64a-2c64b,U+2c72c,U+2c72f,U+2c79f,U+2c7c1,U+2c7fd,U+2c88a,U+2c8d9,U+2c8de,U+2c8e1,U+2c8f3,U+2c907,U+2c90a,U+2c91d,U+2c9c0,U+2ca02,U+2ca0e,U+2ca7d,U+2caa9,U+2cb29,U+2cb2b,U+2cb2d-2cb2e,U+2cb31,U+2cb38-2cb39,U+2cb3b,U+2cb3f,U+2cb41,U+2cb4a,U+2cb4e,U+2cb5a-2cb5b,U+2cb63-2cb64,U+2cb69,U+2cb6c,U+2cb6f,U+2cb73,U+2cb76,U+2cb78,U+2cb7c,U+2cba4,U+2cbb1,U+2cbbf-2cbc0,U+2cbce,U+2cc56,U+2cc5f,U+2ccf5-2ccf6,U+2ccfd,U+2ccff,U+2cd02-2cd03,U+2cd0a,U+2cd8b,U+2cd8d,U+2cd8f-2cd90,U+2cd9f-2cda0,U+2cda8,U+2cdad-2cdae,U+2cdd5,U+2ce05,U+2ce18,U+2ce1a,U+2ce23,U+2ce26,U+2ce2a;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_0e127a.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+45c4-46e8,U+2b9ef,U+2ba74,U+2bac7,U+2bb5f,U+2bb62,U+2bb7c,U+2bb83,U+2bc1b,U+2bc30,U+2bd58,U+2bd77,U+2bd87,U+2bdf7,U+2be29,U+2c029-2c02a,U+2c0a9,U+2c0ca,U+2c1ae,U+2c1d5,U+2c1d9,U+2c1f9,U+2c27c,U+2c288,U+2c2a4,U+2c317,U+2c35b,U+2c361,U+2c364,U+2c3df,U+2c488,U+2c494,U+2c497;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_b028be.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+44a2-45c3,U+2aed0,U+2afa2,U+2b124-2b125,U+2b127-2b128,U+2b137-2b138,U+2b1ed,U+2b300,U+2b328,U+2b363,U+2b36f,U+2b372,U+2b37d,U+2b404,U+2b410,U+2b413,U+2b461,U+2b4e7,U+2b4ef,U+2b4f6,U+2b4f9,U+2b50d-2b50e,U+2b536,U+2b5ae-2b5af,U+2b5b3,U+2b5e7,U+2b5f4,U+2b61c-2b61d,U+2b624,U+2b626-2b628,U+2b62a,U+2b62c,U+2b695-2b696,U+2b6ad,U+2b6de,U+2b6e9,U+2b6ed,U+2b6f6,U+2b728,U+2b7a9,U+2b7c5,U+2b7e6,U+2b7f7,U+2b7f9,U+2b7fc,U+2b806,U+2b80a,U+2b81c,U+2b892,U+2b8b8;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_35c17d.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+434d-44a1,U+2a3ad,U+2a7dd,U+2a8fb,U+2a917,U+2aa07,U+2aa17,U+2aa30,U+2aa36,U+2aa58;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_abac36.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4236-434c,U+29f7e,U+29f83,U+29f8c;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_d829f0.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+40e9-4235,U+289c0,U+28a0f,U+28b46,U+28b49,U+28b4e,U+28c47,U+28c4d,U+28c4f,U+28c51,U+28c54,U+28e99,U+28f7b,U+294d0,U+295d7;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_0f358f.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+3f8b-40e8,U+27c35,U+27ff9,U+280bf,U+2812e,U+28263,U+28408,U+284c8,U+28678,U+28695,U+287e0;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_782503.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4e69,U+4f67,U+4f7e,U+4fdc,U+50e6,U+50ec,U+5196,U+51ab,U+51b1,U+5202,U+5233,U+5261,U+52ac,U+531a,U+5363,U+536e,U+5416,U+5432,U+5454,U+5472,U+5476,U+5478,U+54ad,U+54bb,U+54c2,U+54cf,U+54da,U+54de,U+5537,U+553f,U+5575,U+5581,U+55cc,U+55fe,U+5601,U+560c,U+561e,U+564d,U+566b,U+56dd,U+5709,U+572c,U+572e,U+576b,U+57cf,U+57d2,U+57f8,U+580d,U+581e,U+5844,U+5881,U+589a,U+5924,U+5981,U+5997,U+59a3,U+59b2,U+5ab8,U+5ae0,U+5b34,U+5b5b,U+5ba5,U+5c25,U+5c59,U+5c63,U+5d1e,U+5d24,U+5d6b,U+5e11,U+5e14,U+5e8b,U+5eb3,U+5ed1-5ed2,U+5ef4,U+5efe,U+5f40,U+5f58,U+5f73,U+5f8c,U+5fad,U+600d,U+6019,U+601b,U+602b,U+605a,U+6083,U+6092,U+6100,U+610e,U+623d,U+624c,U+62ca,U+634c,U+6369,U+638e,U+641b,U+6426,U+646d,U+6535,U+65c4,U+65d2,U+6661,U+66db,U+670a,U+6715,U+6753,U+6798,U+67c3,U+67f0,U+681d,U+6855,U+686b,U+68f9,U+6939,U+696e,U+6980,U+698d,U+69b1,U+69ca,U+69ed,U+69f2,U+69ff,U+6a18,U+6aab,U+6b39,U+6b82,U+6b84,U+6b9b,U+6bb3,U+6bf3,U+6bf9,U+6c05-6c06,U+6c0d,U+6c19-6c1a,U+6cb2,U+6cd6,U+6d07,U+6d1a,U+6d27,U+6d2b,U+6d33,U+6d91,U+6dab,U+6e53-6e54,U+6e8f,U+6ebb,U+6f36,U+6fb6,U+7035,U+7085,U+70bb,U+7178,U+71b5,U+7228,U+7256,U+725d,U+726e,U+727e,U+72b0,U+72b4,U+72ce,U+72f7,U+72fa-72fb,U+7313,U+7322,U+738e,U+739f,U+73e5,U+7481,U+74ba,U+74e0,U+750f,U+7540,U+754b,U+755a-755b,U+758b,U+7594,U+75b3,U+760c,U+7615,U+7630,U+7635,U+7640,U+7654,U+76a4,U+76cd,U+775a,U+7765,U+7768,U+77bd,U+77ec,U+781f,U+78a1,U+78d4,U+78d9,U+7905,U+7913,U+79b3,U+79d5,U+79eb,U+7a02,U+7a70,U+7a86,U+7b25,U+7b2a-7b2b,U+7b2e,U+7b31,U+7b38,U+7b47,U+7b62,U+7b6e,U+7b72,U+7b7b,U+7b85,U+7b9c,U+7bcc,U+7bea,U+7bfc,U+7c0b,U+7c0f,U+7c26,U+7c9e,U+7ce8,U+7dae,U+7e47,U+7e9b,U+7f36,U+7f7e,U+7fb0,U+7fbc,U+7fee,U+8016,U+8020,U+802a,U+8031,U+8043,U+8071,U+809f,U+80ab,U+80cd,U+80d7,U+8112,U+8118,U+8160,U+8169,U+8188,U+81a6,U+81c1,U+8234,U+823e,U+825f,U+8298,U+829f,U+82a4,U+82ca,U+82e1,U+82e4,U+82f4,U+82fb,U+8308,U+831a,U+8333,U+83c0,U+83d4,U+83df,U+83f8,U+8401,U+840f,U+841c,U+8451,U+845a,U+846d,U+8476,U+8478,U+84ba,U+84bd,U+851f,U+8548,U+8556,U+855e,U+8585,U+85a4,U+8627,U+8629,U+867a,U+868b,U+868d,U+86b5,U+86c4,U+86de,U+86e9,U+86f8,U+872e,U+8731,U+873e,U+8765,U+877b,U+877d,U+8785,U+87ab,U+87ea,U+87ee,U+880a,U+8813,U+8816,U+8832,U+8864,U+8872,U+887f,U+88a2,U+88b7,U+88c9,U+88ce,U+88f0,U+890a,U+892b,U+897b,U+89da,U+89f3,U+8a48,U+8c55,U+8c73,U+8c78,U+8d91,U+8dba,U+8dcf,U+8deb,U+8e14,U+8e3a,U+8e41,U+8e6f,U+8e90,U+8fb6,U+8fd3,U+8fe8,U+9011,U+902d,U+90be,U+915e,U+9161-9162,U+9179,U+9185,U+918c,U+9190,U+91a3,U+92c8,U+953a,U+961d,U+9654,U+96ce,U+96e9,U+972a,U+9730,U+9785,U+9954,U+9aba,U+9ac0-9ac1,U+9ae1,U+9aeb,U+9aed,U+9af9,U+9b1f,U+9b32,U+9b4d,U+9b51,U+9e87,U+9ee2,U+9f17,U+9f19,U+9f22,U+9f2c,U+9f2f;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_0d29b4.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+3e19-3f8a,U+26d4f,U+277c4;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_5a8449.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+3cc5-3e18,U+260b3,U+260c2,U+26220-26221,U+26246,U+26291,U+26351,U+26369,U+2640c,U+2648d,U+26548,U+26676,U+2677c,U+26b5c,U+26c0f,U+26c21;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_545620.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4e47,U+4e5c,U+4e8d,U+4e93,U+4ebb,U+4ec4,U+4edf,U+4ef5,U+4f32,U+4f5e,U+4f76,U+4f7b,U+4f89,U+5028,U+502c,U+5048,U+5080,U+50ee,U+5121,U+51c7,U+5208,U+520e,U+52f0,U+530f,U+535f,U+53f5,U+5412,U+5443,U+5464,U+5466,U+54a3,U+54a6,U+54bf,U+54d0,U+5533,U+5541,U+5549,U+5550,U+5556,U+555c,U+5576-5577,U+558f,U+5591,U+55b1,U+55dd,U+55e5,U+55f2,U+55f5,U+560f,U+5627,U+562c,U+564c,U+5657-5658,U+5664,U+567b,U+5685,U+56d4,U+56e1,U+571c,U+5769,U+577c,U+578c,U+5793,U+57e4,U+5800,U+5807,U+5889,U+58c5,U+593c,U+59a4,U+59af,U+59d8,U+5a09,U+5a0c,U+5a4a,U+5a62,U+5a67,U+5ad8,U+5aeb,U+5b53,U+5b6c,U+5c3b,U+5c50,U+5c7a,U+5ca2-5ca3,U+5cac,U+5d26-5d27,U+5d6c,U+5d9d,U+5db7,U+5df3,U+5e19,U+5ea0,U+5eb9,U+5ebe,U+5edb,U+5f01,U+5f2d,U+5fb5,U+5fbc,U+5fdd,U+5fea,U+5ff8,U+6029,U+6041-6042,U+60b1,U+6175,U+6215,U+6221-6222,U+623e,U+6308,U+6339,U+638a,U+63e0,U+643d,U+6441,U+647a,U+6485,U+64e2,U+6555,U+659b,U+65ab,U+65c3,U+65c6,U+65f0,U+6600,U+6677,U+668c,U+669d,U+66be,U+66e9,U+66f7,U+6775,U+678b,U+67c1,U+67d2,U+67e2,U+6832,U+6840,U+6874,U+6877,U+68f0,U+68fc,U+690b,U+6910,U+6934,U+6966,U+6978,U+6998,U+69a7,U+69c1,U+69ce,U+6a17,U+6a3d-6a3e,U+6a50,U+6a5b,U+6a84,U+6a97,U+6b8d,U+6baa,U+6bd6,U+6bfd,U+6c10,U+6c16,U+6c18,U+6c3d,U+6c54,U+6cd4,U+6ceb,U+6cee,U+6d04,U+6d0c,U+6d60,U+6ddd,U+6de0,U+6e6b,U+6eb1,U+6ebd,U+6ecf,U+6fde,U+7023,U+70b7,U+70ca,U+7110,U+7168,U+71b3,U+71f9,U+7230,U+723b,U+7284,U+728d,U+7292,U+72d2,U+72d9,U+7301,U+731e,U+7352,U+7360,U+73a2,U+73b3,U+740a,U+7441,U+7457,U+74ff,U+7511,U+75a3,U+75a5,U+75d8,U+75e3-75e4,U+75e7,U+7610,U+7619,U+7622,U+7633,U+7688,U+76b4,U+76e5,U+771a,U+772d,U+779f,U+7812,U+7818,U+7826,U+7829,U+782c,U+784c,U+7850,U+7887,U+789a,U+78b2,U+78f4,U+7957,U+7a39,U+7aa8,U+7ae6,U+7b04,U+7b0f,U+7b1e,U+7b24,U+7bac,U+7bb8,U+7bc1,U+7bd9,U+7c1f,U+7ccc,U+7f45,U+7f5f,U+7f71,U+7fa7,U+7faf,U+7fbf,U+7fd5,U+7fe5-7fe6,U+7ff3,U+801c,U+8052,U+8084,U+8093,U+80b1,U+80bc,U+80db,U+80dd,U+80e4,U+80ef,U+80f4,U+8132,U+815a,U+8167,U+81ca,U+81cc,U+81ec,U+8202,U+8210,U+8228,U+8244,U+8249,U+824f,U+828a,U+828e,U+82a1,U+82a8-82a9,U+82ab,U+82b4,U+82e3,U+8306,U+833a,U+8351,U+8378,U+83a8,U+83aa,U+83d8,U+83e1,U+83ea,U+83f0,U+8497,U+84c1,U+84ca,U+84fc,U+8538,U+853b,U+857b,U+858f,U+859c,U+85ff,U+8605,U+8662,U+867b,U+86a8-86a9,U+86b1,U+86b4,U+86c9,U+86ed,U+870a,U+8722,U+8725,U+8734,U+874c,U+8763,U+876e,U+879f,U+87b3,U+87ca,U+87e5,U+887d-887e,U+8888,U+88df,U+88fe,U+8919,U+8921,U+89e5,U+8c47,U+8c7a,U+8d67,U+8db5,U+8dbf,U+8dd7,U+8dec,U+8dfd,U+8e05,U+8e1f,U+8e59,U+8e69,U+8e85,U+900b,U+9021,U+902f,U+9044,U+9062,U+908b,U+9099,U+909b,U+90b4,U+90c7,U+90eb,U+90fe,U+9123,U+9143,U+9146,U+9172,U+918d,U+91aa,U+91ae,U+91ba,U+93ca,U+96b9,U+96d2,U+9708,U+975b,U+977c,U+992e,U+9955,U+9ab0,U+9ab6-9ab7,U+9ac2,U+9afb,U+9b23,U+9b3b,U+9ecd,U+9ecf,U+9ee7;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_bc06aa.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4e4d,U+4e5e,U+4ea8,U+4ed5,U+4f51,U+4f7c,U+4f83,U+4fae,U+4fd1,U+4fd8,U+4fef,U+4ffa,U+5018,U+5026,U+508d,U+50a3,U+50da,U+50e7,U+50fb,U+515c,U+5162,U+5189,U+5195,U+51a4,U+51f3,U+51f9,U+5203,U+5228,U+5254,U+5256,U+52ff,U+5319,U+5320,U+533e-533f,U+5351,U+5375,U+53db,U+53ed-53ee,U+541f,U+5435,U+543b,U+5475,U+5484,U+548b,U+5490,U+54c9,U+54e9,U+54fa,U+5543,U+5587,U+5589,U+5598,U+55a7,U+55d3,U+560e,U+576f,U+5777,U+57ae,U+57e0,U+5830,U+589f,U+58a9,U+5937,U+5962,U+5974,U+5984,U+59ae,U+59e8,U+59ec,U+5a1f,U+5a23,U+5a36,U+5a3c,U+5a49,U+5a9a,U+5ab3,U+5ac2,U+5ad6,U+5b5a,U+5b99,U+5b9b,U+5bb5,U+5bc5,U+5bc7,U+5bde,U+5be1,U+5be5,U+5c09,U+5c2c,U+5c51,U+5cb1,U+5cd9,U+5cea,U+5d0e,U+5d16,U+5d29,U+5d4c,U+5dcd,U+5deb,U+5e87,U+5e9a,U+5ed3,U+5f13,U+5f57,U+5f64,U+5f87,U+5f8a,U+5f98,U+5fe1,U+5ff1,U+6016,U+6021,U+606a,U+606d,U+6096,U+60bc,U+60d5,U+60df,U+60f9,U+611a,U+614c,U+6177,U+6241,U+6252,U+6254,U+625b,U+626f,U+627c,U+6292,U+6296,U+62b9,U+62c2,U+62cc,U+62ef,U+62f1,U+634f,U+63a0,U+63e3,U+6467,U+6495,U+64bc,U+64c2,U+6518,U+6577,U+6590,U+65a7,U+6627,U+6643,U+6674,U+6687,U+66ae,U+66d9,U+66dd,U+66f0,U+6714,U+673d,U+6789,U+6795,U+67d1,U+67ff,U+6813,U+6829,U+6893,U+68a7,U+68ad,U+68b3,U+68cd,U+68d5,U+68d8,U+68e0,U+690e,U+6930,U+6960,U+6977,U+6986,U+69bb,U+69d0,U+69fd,U+6a1f,U+6b47,U+6b49,U+6b6a,U+6bd7,U+6c13,U+6c1f,U+6c2e-6c2f,U+6c72,U+6c7e,U+6c81,U+6c90,U+6cbc,U+6ccc,U+6cd3,U+6cd7,U+6ce3,U+6d5a,U+6dcc,U+6de4,U+6df3,U+6e1d,U+6e23,U+6e2d,U+6e9c,U+6ea5,U+6ec7,U+6ed4-6ed5,U+6f2f,U+6f33,U+6f62,U+6fa1,U+7011,U+701a,U+707c,U+708a,U+70af,U+70d8,U+70f9,U+711a,U+7130,U+714e,U+716e,U+7194,U+71a0,U+71ac,U+71e5,U+7280-7281,U+72d0,U+72fc,U+7316,U+731c,U+7334,U+7357,U+7384,U+73ab,U+73ca,U+7422,U+7426,U+742a,U+745b-745c,U+745f,U+7470,U+7480,U+74a7,U+7529,U+7578,U+75d5,U+7696,U+76ce,U+7736-7737,U+77a7,U+77bb,U+77d7,U+77e2-77e3,U+77ee,U+780c,U+785d,U+788c,U+789f,U+78c5,U+78ca,U+7901,U+7941,U+796d,U+797a,U+79be,U+79e4,U+79f8,U+7a1a,U+7a3c,U+7a57,U+7a74,U+7a98,U+7a9f,U+7aff,U+7b1b,U+7bf7,U+7c07,U+7c3f,U+7c7d,U+7c9f,U+7ca5,U+7cb9,U+7cd9,U+7cdf,U+7e82,U+7f38,U+7f69,U+7f9e,U+7fdf,U+7fe9,U+800d,U+8018,U+803d,U+803f,U+80a2,U+80aa,U+80d6,U+8116,U+813e,U+8151,U+8155,U+817a,U+819d,U+81fb,U+8205,U+820c,U+821c,U+82ad,U+82af,U+82b9,U+82bd,U+82d1,U+82db,U+82df,U+82ef,U+8304,U+832c,U+8335,U+8354,U+839e,U+8403,U+840c,U+840e,U+8446,U+846b,U+849c,U+84b2,U+84c9,U+8517,U+851a,U+853d,U+8549,U+8587,U+85af,U+85fb,U+8611,U+8650,U+865e,U+868a,U+868c,U+86d9,U+8700,U+8774,U+8776,U+8862,U+88d4,U+88d8-88d9,U+88f8-88f9,U+8912,U+895f,U+8a79,U+8b6c,U+8c41,U+8c79,U+8d81,U+8d9f,U+8dcb,U+8dea,U+8e22,U+8e29,U+8e35,U+8e44,U+8e72,U+8e81,U+8eac,U+8eb2,U+8fe2,U+8fed,U+9017,U+901b,U+902e,U+9038,U+9050,U+906e,U+9091,U+90aa,U+90af,U+90dd,U+914b,U+916f,U+9175-9176,U+918b,U+946b,U+962e,U+9640,U+964c,U+9661,U+9672,U+968b,U+9699,U+96c0,U+9716,U+9756,U+9761,U+97a0,U+97f6,U+99a8,U+9ad3,U+9ae6,U+9e93,U+9ed4;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_cd2170.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+3b81-3cc4,U+2538a,U+2546a,U+25532,U+25562,U+255a8,U+255fd,U+256f1,U+2591a,U+25997,U+25bbe,U+25c7c,U+25cbb,U+25dd1,U+25ed7;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_fdc333.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+3a1a-3b80,U+246d8,U+247a4,U+249db,U+24a0f,U+24a7d,U+24a8c,U+24ac9,U+24b5b,U+24c1c,U+24c48,U+24cc8,U+24d21,U+24d50,U+24ea5,U+25128,U+251e2;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_f9818b.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4e15,U+4e1e,U+4e2b,U+4eb3,U+4ec9,U+4f0e,U+4f3d,U+4f58,U+4f64,U+4f8f,U+4fd0,U+4fdf,U+501c,U+5043,U+5055,U+510b,U+5140,U+51bc-51bd,U+51cb,U+5243,U+527d,U+52be,U+531d,U+5323,U+535e,U+536f,U+53a5,U+53f1,U+541d,U+5429,U+542d-542e,U+5431,U+547b,U+5486,U+548e,U+5492,U+54aa-54ab,U+54af,U+54c6,U+54ce,U+54fd,U+5506,U+5509,U+5511,U+552c,U+553e,U+557b,U+55b3,U+55c5,U+55d2,U+55e1,U+55e6,U+55f7,U+55fd,U+5600,U+5608,U+561f,U+5636,U+5639,U+563b,U+563f,U+5659,U+5662,U+568e,U+5693,U+572a,U+573b,U+574d,U+5768,U+5773,U+577b,U+57a0,U+57b8,U+57c2,U+586c,U+587e,U+5892,U+58d5,U+58f9,U+598a,U+5992-5993,U+59e3,U+5a20,U+5a29,U+5a6a,U+5a7a,U+5ac9,U+5ae3,U+5b17,U+5b62,U+5b7d,U+5b95,U+5ba6,U+5bb8,U+5c15,U+5c4e,U+5c8c,U+5cab,U+5cb7,U+5d34,U+5d3d,U+5d4a,U+5d82,U+5e1a-5e1b,U+5e44,U+5e7a,U+5ea5,U+5eff,U+5f0b,U+5f29,U+5f77,U+5f89,U+5f99,U+5f9c,U+5fa8,U+5fd0-5fd1,U+5fd6,U+5fff,U+6026,U+6035,U+6043,U+6059,U+6063,U+60b4,U+60b8,U+60bb,U+60d8,U+60f0,U+6115,U+612b,U+618e,U+6194,U+61a9,U+61ca,U+61e6,U+61f5,U+620a,U+6248,U+62a8,U+62d7,U+62fd,U+630e,U+637a,U+6390,U+63a3,U+63ac,U+63b0,U+63e9,U+642a,U+64a9,U+64ae,U+6525,U+652b,U+6538,U+655d,U+65bc,U+65cc,U+65ce,U+65d6,U+65ee-65ef,U+6631,U+6636,U+664c,U+665f,U+6666,U+6684,U+66b9,U+66f3,U+6773,U+6777,U+677c,U+6787,U+67b8,U+67d8,U+67de,U+67e9,U+6845,U+684e,U+6886,U+688f,U+68b5,U+693d,U+6963,U+696b,U+6979,U+6994,U+69b7,U+6a35,U+6aac,U+6b59,U+6c26,U+6c50,U+6c69,U+6c74,U+6c76,U+6c8c,U+6cae,U+6ce0,U+6cef,U+6d5c,U+6d8e,U+6d95,U+6db8,U+6dc5,U+6dd6,U+6dde,U+6de6,U+6dfc,U+6e1a,U+6e25,U+6e6e,U+6e9f,U+6ea7,U+6eb4,U+6ef9,U+6f15,U+6f29-6f2a,U+6f78,U+6fa7,U+6fc2,U+6fe1,U+7131,U+714a,U+715c,U+7166,U+7172,U+71b9,U+7317,U+731d,U+7329,U+7337,U+733e,U+7396,U+73b7,U+73e9,U+7425,U+7428,U+742c,U+742e,U+7455,U+7490,U+74f4,U+7519,U+7525,U+753a,U+75b5,U+75b9,U+75d4,U+75de,U+75e2,U+75fc,U+7638,U+7656,U+765c,U+7678,U+768e,U+76bf,U+76c2,U+76d4,U+76f9,U+7728-7729,U+7762,U+776c,U+777d,U+77a0,U+77cd,U+77f8,U+77fd,U+7825,U+7837,U+787c,U+78b4,U+78d0,U+795a,U+795f,U+79ed,U+7a1e,U+7a37,U+7a79,U+7a92,U+7abf,U+7afa,U+7b06,U+7b20,U+7b4f,U+7b60,U+7b75,U+7b8d,U+7bb4,U+7bdd,U+7be1,U+7cb3,U+7cbc,U+7cdc,U+7cef,U+7f44,U+7f54,U+7f8c,U+7f94,U+7fb2,U+7fb9,U+7fce,U+7fe1,U+8004,U+800b,U+8019,U+808b,U+809b,U+80f1,U+8146,U+816e,U+81c0,U+81c3,U+81c6,U+81fc,U+81fe,U+822b,U+82a5,U+82aa,U+82de,U+8309,U+8317,U+832f,U+8340,U+8343,U+8392,U+83a0,U+8431,U+8469,U+84af,U+8543,U+854a,U+8559,U+8568,U+85b0,U+85b9,U+8638,U+869c,U+86c0,U+86e4,U+8713,U+8718,U+87c0,U+87cb,U+87fe,U+8821-8822,U+8882,U+8892,U+88e8,U+88f1,U+8902,U+892a,U+8983,U+8c5a,U+8c82,U+8d73,U+8dbe,U+8e74,U+8e7c,U+8e87,U+8fc2,U+8fe4,U+8fe6,U+900d,U+9035,U+9041,U+9052,U+905b,U+9068,U+9074,U+9095,U+90a1,U+90c5,U+911e,U+9122,U+9149,U+9157,U+916a,U+916e,U+919b,U+93d6,U+9621,U+9631,U+9642,U+968d,U+96c9,U+9706,U+970f,U+97ed,U+99a5,U+9abc,U+9b03,U+9e9d,U+9ebe,U+9edb,U+9edd,U+9f10;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_09a9b3.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+3886-3a19,U+235cb,U+2365c,U+237c4,U+237f4,U+23a3c,U+23b72,U+23c7c,U+23c97-23c98,U+23e23,U+23e8c,U+241fe,U+242ee,U+24350,U+243f6;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_6927bf.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4edd-4ede,U+4ee1,U+4ee8,U+4eeb,U+4f09,U+4f22,U+4f43,U+4f57,U+4f5a,U+4f5d,U+4f6f,U+4fce,U+4fda,U+4ff3,U+4ff8,U+4ffe,U+500c,U+500f,U+5025,U+502d,U+504e,U+5088,U+50f3,U+5106,U+5241,U+525c,U+52ad,U+530d,U+5310,U+5345,U+539d,U+53df,U+53fb-53fc,U+5420-5421,U+544b,U+5477,U+5482,U+54a7,U+54cc,U+54e7,U+550f,U+5514,U+5530,U+5544,U+5555,U+557e,U+558b,U+5594,U+5599,U+559f,U+55b9,U+55c4,U+55d1,U+55d4,U+55d6,U+55df,U+55e4,U+55e8,U+55ea,U+55ef,U+5623,U+562d,U+564e,U+5654,U+5671,U+56d7,U+56eb,U+56f9,U+5704,U+576d,U+57a1,U+57d5,U+57ed,U+584d,U+5880,U+58ec,U+5914,U+5958,U+599e,U+59be,U+59dd,U+5a11,U+5a40,U+5a55,U+5abe,U+5ae1,U+5ae6,U+5b40,U+5b51,U+5b71,U+5b93,U+5bee,U+5c9c,U+5cc1,U+5ccb,U+5cd2,U+5d06,U+5d2e,U+5d47,U+5d4b,U+5d6f,U+5d99,U+5dfd,U+5e54,U+5e5b,U+5e61,U+5e96,U+5fd2,U+600f,U+6014,U+606b,U+608c,U+609a,U+60c6,U+60da,U+60f4,U+60fa,U+61ff,U+621b,U+621f,U+622e,U+62bb,U+62bf,U+62c8,U+634b,U+636d,U+6371,U+6387,U+63ae,U+63c4,U+63c6,U+63cd,U+63d6,U+63f6,U+6410,U+6414,U+6421,U+64c0,U+64d8,U+661d,U+6641,U+6657,U+66dc,U+6710,U+6748,U+6772,U+67b0,U+67b7,U+6841,U+6849,U+6954,U+695d,U+699b,U+69ab,U+6a28,U+6a47,U+6a58,U+6b46,U+6c21,U+6c24,U+6c35,U+6c4a,U+6c68,U+6c86,U+6c8f,U+6c94,U+6cc5,U+6d2e,U+6d35,U+6d39,U+6d63,U+6d6f,U+6d94,U+6dd9,U+6dec,U+6e4e,U+6e98,U+6ec2,U+6ed3,U+6f09,U+6f66,U+6f8d,U+6fb9,U+6fc9,U+6fe0,U+6fef,U+705e,U+7094,U+7119,U+712f,U+71e7,U+723f,U+726f,U+729f,U+7325,U+7350,U+73c8,U+73d9,U+73de,U+741a,U+7430,U+74e4,U+753e,U+7548,U+7566,U+7579,U+757f,U+7583,U+759d,U+75b8,U+75bd,U+75c2,U+75cd,U+75f1,U+75ff,U+7634,U+7699,U+76c5,U+76f1,U+7708,U+7719,U+777e,U+778c,U+7791,U+77b3,U+77dc,U+781d,U+7823,U+7827,U+782d,U+7830,U+783c,U+7889,U+7893,U+78a3,U+78ec,U+7949,U+795c,U+79e3,U+7a14,U+7a17,U+7a88,U+7a95,U+7aa0,U+7afd,U+7b08,U+7b33,U+7b90,U+7b95,U+7be6,U+7bfe,U+7c0c,U+7c2a,U+7c91,U+7cb2,U+7cc5,U+7da6,U+7f58,U+7f61,U+7fb8,U+7fca,U+8006,U+8012,U+8026,U+8037,U+807f,U+80bd,U+80ed,U+814b,U+8153,U+8171,U+8174,U+8198,U+8200,U+8214,U+821b,U+8222,U+826e,U+828d,U+82be,U+82c4,U+82d2,U+82d5,U+82dc,U+82eb,U+82f7,U+830c,U+832d,U+8331,U+8334,U+834f,U+83c5,U+83d6,U+83fd,U+840b,U+8418,U+8438,U+843c,U+8473,U+847a,U+84a1,U+84d6,U+84ff,U+852b,U+8564,U+85d0,U+8693,U+86a3-86a4,U+86a7,U+86aa,U+86af,U+86b6,U+86c6,U+86d0,U+86f9,U+8703,U+8707-8709,U+870d,U+8737,U+873b,U+8759,U+8760,U+8782,U+87af,U+87c6,U+87d1-87d2,U+87e0,U+8815,U+8839,U+8869,U+8913,U+8936,U+8a07,U+8a3e,U+8b07,U+8c49,U+8c4c,U+8c62,U+8c89,U+8d6d,U+8d84,U+8d94,U+8dc6,U+8dce,U+8ddb,U+8dfa,U+8e09,U+8e1d,U+8e2e,U+8e31,U+8e39,U+8e3d,U+8e42,U+8e47,U+8e49,U+8e4b,U+8e6d,U+8e76,U+9004-9005,U+9016,U+9036,U+9051,U+907d,U+9082,U+9088,U+9097,U+90b0,U+90d7,U+90dc,U+90e2,U+90ef,U+9104,U+914a,U+9150,U+9169,U+919a,U+91b4,U+938f,U+965f,U+9697,U+96b0,U+96bc,U+970e,U+9739,U+973e,U+9798,U+97a3,U+9997,U+9aef,U+9e82,U+9edc,U+9edf-9ee0,U+9f3e;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_f7ca8a.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4e0c,U+4e28,U+4e36,U+4e3f,U+4ea0,U+4ec2,U+4ef3,U+4f74,U+4f91,U+4f94,U+4fc5,U+502e,U+50ba,U+50d6,U+50ed,U+5107,U+5155,U+5182,U+51f5,U+5216,U+525e,U+5281-5282,U+5290,U+5293,U+52d6,U+52f9,U+5369,U+53b6,U+54a9,U+54ff,U+552a,U+553c,U+5588,U+55b5,U+55c9,U+55cd,U+5686,U+570a,U+572f,U+574c,U+5776,U+57a4,U+57f4,U+57fd,U+580b,U+5819,U+5820,U+5865,U+58bc,U+5902,U+5925,U+59d2,U+5ab5,U+5b16,U+5b32,U+5b65,U+5b80,U+5b84,U+5be4,U+5c1c,U+5c22,U+5c6e,U+5c88,U+5c8d,U+5cb5,U+5d3e,U+5ddb,U+5e80,U+5ee8,U+5f50,U+5f56,U+5f61,U+5f82,U+5fc4,U+5fc9,U+5fe4,U+5fed,U+600a,U+605d,U+6067,U+609b,U+609d,U+60dd,U+6106,U+614a,U+615d,U+619d,U+6224-6225,U+6243,U+62f6,U+6343,U+63ad,U+63be,U+63ce,U+63de,U+63f2,U+640b-640c,U+6420,U+6434,U+643f,U+6496,U+64d0,U+64d7,U+64e4,U+6509,U+652e,U+6534,U+6549,U+656b,U+6603,U+6634,U+674c,U+676a,U+67b5,U+67d9,U+67dd,U+6833,U+6844,U+6883,U+6957,U+6971,U+69d4,U+6a8e,U+6a91,U+6aa0,U+6b37,U+6b43,U+6bea,U+6c15,U+6c2a,U+6c46,U+6c5c,U+6cd0,U+6d0e,U+6d7c,U+6e2b,U+6eb2,U+6eb7,U+6f2d,U+6f72,U+6f89,U+6f8c,U+7039,U+706c,U+70b1,U+70c0,U+7113,U+71d4,U+71e0,U+721d,U+727f,U+728b,U+728f,U+72c1,U+72c3,U+72e8,U+72f3-72f4,U+730a,U+7331,U+7338-7339,U+734d,U+736c,U+736f,U+73e7,U+746d,U+74a9,U+74de,U+750d,U+7513,U+752f,U+754e,U+7592,U+75c3-75c4,U+75e6,U+760a,U+761b,U+7625,U+762d,U+763c,U+7643,U+764d,U+7704,U+7707,U+7722,U+7735,U+7743,U+7747,U+7780,U+778d,U+77a2,U+77b5,U+77e7,U+7809,U+7811,U+7839,U+7847,U+784e,U+786a,U+786d,U+78a5,U+78c9,U+78f2,U+791e,U+7924,U+793b,U+7946,U+7953,U+7967,U+798a,U+7a03,U+7a78,U+7a80,U+7aac,U+7ab3,U+7b0a,U+7b45,U+7b4c,U+7b58,U+7b9d,U+7ba2,U+7bda,U+7be5,U+7c40,U+7ca2,U+7cc8,U+7cd7,U+7cf8,U+7e3b,U+7f68,U+7f9d,U+8025,U+8028-8029,U+8035,U+8080,U+809c,U+80ad,U+80c2,U+80e9,U+80ec,U+80f2,U+810e,U+811e,U+812c,U+8182,U+8201,U+8204,U+8221,U+822d,U+822f,U+8233,U+824b,U+825a,U+8268,U+8274,U+827d,U+827f,U+8284,U+828f,U+8291,U+82b0,U+82e0,U+8307,U+831b,U+833c,U+8347,U+837d,U+839b,U+83a9,U+83b0,U+83dd,U+83e5,U+83f9,U+8406,U+8459,U+845c,U+8488,U+848e,U+84b4,U+84b9,U+84cd,U+84d0,U+84f0,U+850c,U+8579-857a,U+85a8,U+85b7,U+863c,U+864d,U+867c,U+86b0,U+86ba,U+86d1,U+86d8,U+871e,U+8723,U+8729,U+8753,U+8764,U+8770,U+8788,U+878b,U+8793,U+8797,U+87ac-87ad,U+87b5,U+87bd,U+87d3,U+87db,U+8803,U+881b,U+883c,U+8844,U+88bc,U+88d2,U+88fc,U+8930,U+895e,U+8966,U+89d6,U+89eb,U+8b26,U+8c85,U+8c8a,U+8c94,U+8c98,U+8dbc,U+8de3,U+8e23,U+8e40,U+8e94,U+8e9e,U+8ece,U+8fd5,U+9058,U+90b6,U+90db,U+914e-914f,U+9164,U+9174,U+9191,U+91a2,U+91ad,U+91af,U+91b5,U+928e,U+936a,U+943e,U+955f,U+9622,U+963c-963d,U+966c,U+9674,U+9688,U+96b3,U+9794,U+97ab,U+9998,U+9ab1,U+9ad1,U+9adf,U+9b08,U+9b0f,U+9b2f,U+9b43,U+9b48,U+9eb4,U+9ee5,U+9ef9,U+9efb-9efc,U+9f37,U+9f3d,U+9fa0;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_bf7217.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+3702-3885,U+2295e,U+2298f,U+22ad3,U+22aec,U+22b38,U+22ba5,U+22cdf,U+22d80,U+22dec,U+22f96,U+23519;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_5b1e0f.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4e01,U+4e39,U+4e43,U+4e4f,U+4e58-4e59,U+4e95,U+4ea1,U+4ea6,U+4eab,U+4eae,U+4ec1,U+4ef2,U+4f0a,U+4f0f-4f11,U+4f2f-4f30,U+4f34,U+4f38,U+4f3c,U+4f69,U+4f73,U+4faf,U+4fb5,U+4fd7,U+4ff1,U+5012,U+5021,U+504f,U+5085,U+5141,U+5144,U+5146,U+5192,U+51a0,U+51b0,U+51b6-51b7,U+51dd,U+51e1,U+5200,U+5211,U+5237,U+523a,U+5272,U+52a3,U+52c3,U+52c7,U+52d2,U+52d8,U+52e4,U+5353,U+539a,U+53e5,U+53eb,U+5401,U+541b,U+5427,U+542b,U+5448,U+54e5,U+54ea,U+5510,U+552f,U+5531,U+554a,U+559d,U+5609,U+5740,U+5750,U+5761,U+5766,U+57c3,U+5802,U+5806,U+5821,U+5851,U+5854,U+585e,U+586b,U+58a8,U+58c1,U+5915,U+5949,U+594f,U+5954,U+5960,U+5976,U+5999,U+59a5,U+59b9,U+59bb,U+59c6,U+59d0-59d1,U+59d3,U+5a5a,U+5a92,U+5b54,U+5b5f,U+5b64,U+5b85,U+5b87-5b88,U+5b8b,U+5b97,U+5b9c,U+5bbf,U+5bc4,U+5bd2,U+5be8,U+5c01,U+5c04,U+5c0a,U+5c16,U+5c24,U+5c3e,U+5c4b,U+5c65,U+5cfb,U+5d07,U+5de1,U+5de7,U+5e1d,U+5e7c,U+5ec9,U+5ef7,U+5f04,U+5f1f,U+5f31,U+5f6d,U+5f70,U+5f79,U+5f92,U+5faa,U+5fbd,U+5fd8-5fd9,U+5fe0,U+5ffd,U+6015,U+602a,U+6050,U+6062,U+6069,U+6070,U+6084,U+6089,U+60a3,U+60a8,U+60b2,U+60dc,U+6162,U+6167,U+6170,U+61c2,U+622a,U+6234,U+626d,U+6291,U+62b1,U+62bc-62bd,U+62c6,U+62d2-62d4,U+62d6,U+62f3,U+6311,U+6316,U+6350,U+6355,U+6377,U+6389,U+638c,U+6398,U+63cf,U+63e1,U+63ed,U+63f4,U+640f,U+642c-642d,U+6458,U+6469,U+6478,U+64a4,U+64cd,U+6500,U+653b,U+654f,U+6562,U+6566,U+656c,U+658c,U+65c1,U+65e6,U+65e8,U+65ec,U+65f1,U+65fa,U+660c,U+6628,U+6668,U+667a,U+6696,U+66b4,U+66f9,U+66fc,U+66ff,U+670b,U+6717,U+672b,U+675c,U+676d,U+6790,U+679a,U+679d,U+67b6,U+67ec,U+67f1,U+67f3-67f4,U+683d,U+6842,U+684c,U+6851,U+6885,U+68af-68b0,U+68ee,U+695a,U+699c,U+6b20,U+6b23,U+6b8a,U+6b96,U+6bc5,U+6bcd,U+6bd2,U+6beb,U+6c0f,U+6c1b,U+6c57,U+6c60,U+6c6a,U+6c89,U+6cc9,U+6cdb,U+6ce5,U+6cf0,U+6cf3,U+6d17,U+6d1b,U+6d1e,U+6d3d,U+6d59,U+6d66,U+6d69-6d6a,U+6d6e,U+6d82,U+6db2,U+6db5,U+6de1,U+6dee,U+6df7,U+6dfb,U+6e20-6e21,U+6e58,U+6eaa,U+6ecb,U+6ed1,U+6f0f,U+6f20,U+6f2b,U+6f58,U+704c,U+7070,U+708e,U+70ad,U+70b8,U+7126,U+714c,U+718a,U+719f,U+71c3,U+71d5,U+7206,U+7236,U+7238,U+7259,U+725b,U+7262,U+7267,U+72e0,U+731b,U+73bb,U+73cd,U+73e0,U+741b,U+7434,U+745e,U+7483,U+74dc,U+74e6,U+74f7,U+7518,U+7532,U+755c,U+756a,U+7586,U+7591,U+75ab,U+75be,U+75db,U+764c,U+7687,U+76c8,U+76df,U+76f2,U+76fe,U+7761,U+77db,U+786c,U+7891,U+78c1,U+78e8,U+795d,U+7965,U+7981,U+79d2,U+79df,U+79e6,U+79e9,U+7a00,U+7a3b,U+7a3f,U+7a46,U+7a7f,U+7a97,U+7adf,U+7aef,U+7af9,U+7b11,U+7b26,U+7bb1,U+7bc7,U+7c4d,U+7c89,U+7c97,U+7f8a,U+7fbd,U+7ffb,U+8000,U+8010,U+8015,U+8017,U+8033,U+8058,U+805a,U+8089,U+8096,U+809d,U+80a5,U+80a9,U+80af,U+80de,U+80e1,U+8150,U+817f,U+81a8,U+81e3,U+8212,U+827e,U+82ac,U+82b3,U+82d7,U+82e5,U+8302,U+8336,U+8352,U+8377,U+83ab,U+83f2,U+8461,U+8463,U+8499,U+84c4,U+852c,U+8584,U+864e,U+86cb,U+8861,U+8863,U+8870,U+8877,U+8881,U+888b,U+88c1-88c2,U+88d5,U+8986,U+8c46,U+8c6a,U+8c8c,U+8d64,U+8d6b,U+8d74,U+8da3,U+8dd1,U+8ddd,U+8ddf,U+8de8,U+8df3,U+8e0f,U+8f9b,U+8fd4,U+8fea-8feb,U+8ff7,U+9003,U+900f,U+9014,U+906d,U+9075,U+907f-9080,U+90a6,U+90ca,U+90ce,U+90ed,U+9192,U+91ce,U+963b,U+9644,U+966a,U+9675-9677,U+9686,U+9694,U+96c5,U+96d5,U+96e8,U+96ea,U+96f6-96f7,U+9707,U+971e,U+9732,U+978b,U+9910,U+9aa8,U+9ebb,U+9ece,U+9ed8;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_971f00.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+3569-3701,U+21681,U+216df,U+21a18,U+21a25,U+21b36,U+21deb,U+21ed5,U+2231a;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_df3eef.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+3001-3002,U+3008-3011,U+3014-3017,U+4e00,U+4e03,U+4e09-4e0b,U+4e0d,U+4e14,U+4e16,U+4e2d,U+4e3b,U+4e45,U+4e4b,U+4e4e,U+4e5d,U+4e5f,U+4e88,U+4e8b-4e8c,U+4e92,U+4e94,U+4e9b,U+4ea4,U+4eac,U+4eba,U+4ec0,U+4eca-4ecb,U+4ecd,U+4ed6,U+4ed8,U+4ee3-4ee5,U+4ef6,U+4efb,U+4efd,U+4f01,U+4f0d,U+4f46,U+4f4d-4f4f,U+4f55,U+4f5c,U+4f7f,U+4f8b,U+4f9b,U+4f9d,U+4fbf,U+4fc3-4fc4,U+4fdd,U+4fe1,U+500d,U+5019,U+503c,U+5047,U+505a,U+505c,U+5065,U+50cf,U+5143,U+5145,U+5148-5149,U+514d,U+5165,U+5168,U+516b-516d,U+5171,U+5175-5178,U+517c,U+518d,U+5206-5207,U+520a,U+5217,U+521d,U+5224,U+5229,U+5230,U+5238,U+523b,U+524d,U+526f,U+529b,U+529f-52a0,U+52a9-52aa,U+5305,U+5316-5317,U+5341,U+5347-5348,U+534a,U+5357,U+535a,U+5361,U+5370-5371,U+5382,U+539f,U+53bb,U+53c8,U+53ca-53cb,U+53cd,U+53d6-53d7,U+53e3-53e4,U+53e6,U+53ec,U+53ef-53f0,U+53f2-53f3,U+53f8,U+5404,U+5409,U+540d,U+5411,U+5426,U+5438,U+544a,U+5462,U+5468,U+5473,U+547c-547d,U+548c,U+54c1,U+54c8,U+552e,U+5546,U+5584,U+559c,U+5668,U+56db,U+56e0,U+56fa,U+571f,U+5728,U+5730,U+5733,U+5747,U+578b,U+57ce,U+57df,U+57f9-57fa,U+5883,U+589e,U+58eb,U+590f,U+5916,U+591a,U+591c,U+5927,U+5929-592a,U+592e,U+5931,U+5947,U+5957,U+5973,U+5979,U+597d,U+5982,U+59cb,U+59d4,U+5a01,U+5b50,U+5b57-5b58,U+5b63,U+5b69,U+5b89,U+5b8c,U+5b8f,U+5b98,U+5b9a,U+5ba2-5ba4,U+5bb3,U+5bb9,U+5bc6,U+5bcc,U+5bdf,U+5c0f,U+5c11,U+5c1a,U+5c31,U+5c3c,U+5c45,U+5c55,U+5c71,U+5cb8,U+5ddd-5dde,U+5de5-5de6,U+5dee,U+5df1-5df2,U+5df4,U+5e02,U+5e0c,U+5e38,U+5e45,U+5e55,U+5e73-5e74,U+5e8f,U+5e95,U+5e97,U+5e9c,U+5ea6-5ea7,U+5ead,U+5eb7,U+5ef6,U+5efa,U+5f0f,U+5f15,U+5f62,U+5f71,U+5f80,U+5f85,U+5f88,U+5f8b,U+5f90,U+5f97,U+5fae,U+5fb7,U+5fc3,U+5fc5,U+5feb,U+600e,U+601d,U+6025,U+6027,U+606f,U+60c5,U+60e0,U+60f3,U+610f,U+611f,U+6210-6211,U+6216,U+623f-6240,U+624b,U+6253,U+6276,U+6279,U+627e-6280,U+628a,U+6293,U+6295,U+6297,U+62c9,U+62cd,U+62db,U+62ec,U+62ff,U+6301,U+6307,U+6309,U+632f,U+6388,U+6392,U+63a2,U+63a5,U+63a7-63a8,U+63aa,U+63d0,U+641e,U+64ad,U+652f,U+6536,U+6539,U+653e-653f,U+6545,U+6548,U+6551,U+6563,U+6574,U+6587,U+6599,U+65a4,U+65af-65b0,U+65b9,U+65bd,U+65c5,U+65cf,U+65e5,U+65e9,U+660e,U+6613,U+661f-6620,U+6625,U+662f,U+665a,U+666e-666f,U+66f2,U+66f4,U+66fe,U+6700,U+6708-6709,U+670d,U+671b,U+671d,U+671f,U+6728,U+672a,U+672c,U+674e,U+6750-6751,U+675f,U+6797,U+67d0,U+67d3,U+6821,U+6839,U+683c,U+6848,U+68c9,U+690d,U+6a21,U+6b21,U+6b3e,U+6b4c,U+6b62-6b66,U+6b7b,U+6bb5,U+6bcf,U+6bd4,U+6bdb,U+6c11,U+6c34,U+6c38,U+6c42,U+6c5f,U+6c7d,U+6c99,U+6cb3,U+6cb9,U+6cbb,U+6cbf,U+6cd5,U+6ce2,U+6d0b,U+6d25,U+6d2a,U+6d32,U+6d3b,U+6d3e,U+6d41,U+6d77,U+6d88,U+6df1,U+6e05,U+6e2f,U+6e56,U+6e90,U+6f14,U+6f6e,U+6fb3,U+6fc0,U+706b,U+70c8,U+7136,U+7164,U+7167,U+7247-7248,U+724c,U+7269,U+7279,U+72af,U+7387,U+7389,U+738b,U+73ed,U+7403,U+7406,U+751a,U+751f,U+7528,U+7530-7531,U+7533,U+7537,U+754c,U+7559,U+7565,U+75c5,U+767b,U+767d-767e,U+7684,U+76ae,U+76ca,U+76db,U+76ee,U+76f4,U+76f8,U+770b,U+773c,U+7763,U+77e5,U+77ed,U+77f3,U+7814,U+7834,U+793a,U+793e,U+7956,U+795e,U+7968,U+798f,U+79c0,U+79d1,U+79fb,U+7a0b,U+7a76,U+7a7a,U+7a81,U+7acb,U+7ad9,U+7ae0,U+7ae5,U+7b2c,U+7b49,U+7b54,U+7b56,U+7b97,U+7ba1,U+7c73,U+7cbe,U+7cfb,U+7d20,U+7d22,U+7e41,U+7f3a,U+7f6a,U+7f6e,U+7f72,U+7f8e,U+8001,U+8003,U+8005,U+800c,U+80a1,U+80b2,U+80fd,U+81ea,U+81f3,U+821e,U+822a,U+822c,U+8239,U+826f,U+8272,U+82b1,U+82e6,U+82f1,U+8349,U+83dc,U+843d,U+8457,U+85cf,U+878d,U+8840,U+884c,U+8857,U+88ab,U+897f,U+8981,U+89d2,U+89e3,U+8a00,U+8b66,U+8c61,U+8d70,U+8d77,U+8d85,U+8d8a,U+8db3,U+8def,U+8eab,U+8fc5,U+8fce,U+8fd1,U+8ff0,U+8ffd,U+9000-9001,U+9010,U+901a,U+901f-9020,U+9047,U+904d,U+9053,U+90a3,U+90e8,U+90fd,U+914d,U+9152,U+91c7,U+91cd,U+91cf,U+91d1,U+9632,U+963f,U+964d,U+9650,U+9662,U+9664,U+969c,U+96c4,U+96c6,U+9700,U+9752,U+975e,U+9760,U+9762,U+9769,U+97f3,U+98df,U+9996,U+9999,U+9ad8,U+9f13,U+fe10-fe19,U+fe30-fe31,U+fe33-fe44;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_8867bc.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+27e8-27e9,U+2e81,U+2e84,U+2e88,U+2e8b-2e8c,U+2e97,U+2ea7,U+2eaa,U+2eae,U+2eb3,U+2eb6-2eb7,U+2ebb,U+2eca,U+2ff0-2ffb,U+3000,U+3003,U+3005-3007,U+3012-3013,U+3018-301b,U+301d-301e,U+3021-3029,U+303e,U+3105-3129,U+31c0-31e3,U+3220-3229,U+3231,U+32a3,U+338e-338f,U+339c-339e,U+33a1,U+33c4,U+33ce,U+33d1-33d2,U+33d5,U+3400-3568,U+20087,U+20089,U+200cc,U+20164,U+201ee,U+20242,U+20302,U+20542,U+20545,U+20587,U+20676,U+207ad,U+207b0,U+20a44,U+20ab1,U+20b6f,U+20bbe,U+20cd0,U+20d1f,U+20dff,U+20ef3,U+20f1f,U+2139a,U+21413,U+2144d,U+215d7;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_fdb231.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+20-7e,U+a0-17f,U+18f,U+192,U+1a0-1a1,U+1af-1b0,U+1cd-1dc,U+1e5,U+1e7,U+1e9,U+1ef-1f0,U+1f9-1ff,U+218-21b,U+21f,U+237,U+2c6-2c7,U+2c9,U+300,U+302,U+304,U+30c,U+ff04,U+ff0b,U+ff1c-ff1e,U+ff3e,U+ff40,U+ff5c,U+ff5e,U+ffe0-ffe5;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_d95e9e.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+50b4,U+50e8,U+5110,U+51aa,U+5244,U+528c,U+52f1,U+532d,U+53b4,U+54bc,U+551a,U+5645,U+5666,U+5672,U+568c,U+56a6,U+56b3,U+56c5,U+5852,U+58da,U+5969,U+5abc,U+5b24,U+5b4c,U+5c68,U+5d2c,U+5d81,U+5df0,U+5df9,U+5e6c,U+5f12,U+5f33,U+6128,U+6192,U+61ae,U+61cd,U+61df,U+6207,U+6214,U+6227,U+6451,U+6463,U+649f,U+6504,U+68d6,U+68ec,U+69aa,U+69bf,U+69e7,U+6a48,U+6ae7,U+6aeb,U+6af3,U+6e5e,U+6e63,U+6f59,U+6f77,U+6fa9,U+6ffc,U+7027,U+7197,U+736a-736b,U+7380,U+7602,U+761e,U+7649,U+7667,U+76b8,U+7798,U+78fd,U+79b0,U+7a6d,U+7ab6,U+7c5c,U+7c69-7c6a,U+7cdd,U+7cf4,U+7cf6,U+7cf9,U+7d06-7d07,U+7d32,U+7d86,U+7d88,U+7dc4,U+7dd9,U+7de6,U+7df6,U+7df9,U+7e0b,U+7e11,U+7e2d,U+7e32,U+7e36,U+7e52,U+7e62,U+7e6f,U+7e8a,U+7f86,U+7f8b,U+802e,U+8079,U+8156,U+8161,U+8181,U+81da,U+81e0,U+8264,U+826b,U+8452,U+851e,U+8526,U+8546,U+8552,U+8558,U+8562,U+8577,U+859f,U+85f6,U+861e,U+863a,U+86fa,U+87e3,U+87ef,U+8806,U+8810,U+8933,U+8946,U+8949,U+895d,U+89a1,U+89af,U+89bf,U+89f6,U+8a01,U+8a75,U+8a84,U+8ad1,U+8ad7,U+8b6b,U+8b7e,U+8b9e,U+8d10,U+8db2,U+8e60,U+8e92,U+8ee4,U+8ef9-8efa,U+8f05,U+8f07,U+8f0a,U+8f61-8f62,U+91c3,U+91d9,U+9201,U+9225,U+9233,U+923d,U+9255,U+929a,U+92a9,U+92b1,U+92cf,U+92dd,U+92df,U+92e3,U+92e6,U+92f6,U+9301,U+9306-9308,U+9312,U+931f,U+9343,U+9346,U+9364,U+9369,U+9370,U+9384,U+9387,U+93bf,U+93dd,U+93f7,U+93f9,U+9412,U+9414,U+9420,U+9426,U+9428,U+944a,U+9465,U+9479,U+958c,U+95ab,U+95bd,U+95e5,U+9689,U+96c8,U+979d,U+97bd,U+97dd,U+980a,U+980f,U+9856,U+9859,U+9862,U+98eb,U+98f1,U+9931,U+9933,U+9937,U+993c,U+994a,U+995c,U+99d4,U+99d8,U+9a0d,U+9a36,U+9a42,U+9a4f,U+9b83,U+9b92,U+9b9a,U+9b9e,U+9baa,U+9bc1,U+9bdd,U+9bea,U+9c08,U+9c12,U+9c28-9c29,U+9c35,U+9c39,U+9c3e,U+9c5d,U+9c5f,U+9c67,U+9c6d,U+9c7a,U+9d1f,U+9d2f-9d30,U+9d42,U+9d53,U+9d5c,U+9d6f,U+9d87,U+9d93,U+9d98,U+9da5,U+9dbc,U+9dd9-9dda,U+9de5-9de6,U+9def,U+9e0c,U+9e7a,U+9ecc,U+9ef2,U+9ef7,U+9f09,U+9f34,U+9f4e-9f4f,U+9f54,U+9f59;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_8a2aad.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4e08,U+4e18-4e19,U+4e32,U+4e38,U+4e52-4e53,U+4e73,U+4e7e,U+4e9f,U+4ead,U+4ed4,U+4ed7,U+4ed9,U+4ef0,U+4f50,U+4fcf,U+502a,U+5076,U+50ac,U+50b2,U+5112,U+5154,U+5180,U+51f0,U+51fd,U+524a,U+526a,U+52ab,U+52c9,U+52df,U+52fe,U+5306,U+5308,U+532a,U+5339,U+5349,U+5378,U+537f,U+5384,U+53c9,U+53d4,U+5410,U+5439,U+543e,U+5440,U+5496,U+54ac,U+54b1,U+54c0,U+54e8,U+54ed,U+5561,U+5564-5566,U+5580,U+558a,U+55bb,U+561b,U+5634,U+56ca,U+572d,U+573e,U+574a,U+574e,U+5751,U+5764,U+576a,U+5782-5783,U+57cb,U+57d4,U+5824,U+582a,U+5835,U+584c,U+5858,U+5885,U+5893,U+58e4,U+5948,U+594e,U+5951,U+59a8,U+59da,U+59fb,U+59ff,U+5a03,U+5a1c,U+5a46,U+5ac1,U+5acc,U+5ae9,U+5b55,U+5b5c-5b5d,U+5bb0,U+5bb4,U+5bc2,U+5bd3,U+5bf8,U+5bfa,U+5c39-5c3a,U+5c3f,U+5c48,U+5c60,U+5c6f,U+5c90,U+5d14,U+5d1b,U+5df7,U+5dfe,U+5e06,U+5e15,U+5e37,U+5e3d,U+5e62,U+5e7b,U+5e7d,U+5eb8,U+5eca,U+5ed6,U+5f0a,U+5f17-5f18,U+5f6a,U+5f6c,U+5f7c,U+5fcc-5fcd,U+6012,U+6028,U+6068,U+6094,U+609f-60a0,U+60d1,U+6101,U+6109,U+6127,U+6148,U+614e,U+6155,U+6168,U+61be,U+61c8,U+6208,U+6212,U+6247,U+626e,U+6284,U+62ab,U+62d0,U+62d8,U+62da,U+62fc,U+62fe,U+632a-632b,U+633a,U+6349,U+6367,U+6380,U+638f,U+63a9,U+63d2,U+6454,U+6492,U+649e,U+64b0,U+64c5,U+64e6,U+655e,U+6572,U+6591,U+659c,U+65a5,U+65ed,U+6602,U+660f,U+6614,U+662d,U+6664,U+6670,U+6676,U+6691,U+66a8,U+6749,U+674f,U+67af,U+67cf,U+67d4,U+67ef,U+682a,U+6843,U+6846,U+6850,U+6854,U+6876,U+68a8,U+68d2,U+68da,U+68f5,U+6905,U+6912,U+6a0a,U+6a61,U+6b3a,U+6b67,U+6b79,U+6bb7,U+6bbf,U+6bef,U+6c27-6c28,U+6c41,U+6c55,U+6c5b,U+6c5d,U+6c70,U+6c82-6c83,U+6c9b,U+6cab,U+6cb8,U+6cca,U+6ce1,U+6cf5,U+6d74,U+6d78,U+6daf,U+6dc4,U+6dcb,U+6dd1,U+6dd8,U+6deb,U+6df9,U+6e24,U+6e34,U+6e5b,U+6ea2,U+6eb6,U+6ef4,U+6f02,U+6f06,U+6f6d,U+6f84,U+7092,U+70ac,U+70b3,U+70e4,U+70ef,U+710a,U+7199,U+722c,U+723d,U+725f,U+7261,U+7272,U+72ac,U+72c2,U+72d7,U+73b2,U+7433,U+751c,U+752b,U+7538,U+754f,U+7554,U+758f,U+75b2,U+75bc,U+7624,U+7626,U+7686,U+76c6,U+76d2,U+76ef,U+76fc,U+7709,U+7720,U+775b,U+7766,U+7779,U+7784,U+77ac,U+7802,U+780d,U+7838,U+786b,U+788e,U+7897-7898,U+78a7,U+78b0,U+78b3,U+78cb,U+78f7,U+79b9,U+79bd,U+79c9,U+79e7,U+7a0d,U+7a3d,U+7a84,U+7ae3,U+7aed,U+7b52,U+7bad,U+7c92,U+7c98,U+7cca,U+7d2b,U+7f50,U+7f55,U+7fc1,U+7fc5,U+7fd4,U+7fe0,U+7ff0,U+7ffc,U+8036,U+804a,U+8086-8087,U+808c,U+809a,U+80ba,U+80c3,U+80ce,U+80f8,U+8102,U+8106,U+810a,U+8154,U+8170,U+8179,U+8180,U+818f,U+819c,U+81c2,U+81ed,U+821f,U+8236,U+8247,U+8258,U+8292,U+829d,U+8305,U+8328,U+832b,U+8389,U+838e,U+83c7,U+83ca,U+83cc,U+83f1,U+8404,U+840d,U+845b,U+846c,U+8482,U+84b8,U+84ec,U+8513,U+8521,U+857e,U+859b,U+85aa,U+85e4,U+8679,U+86c7,U+8702,U+871c,U+87ba,U+87f9,U+884d,U+886b,U+8896,U+88b1,U+8944,U+8a93,U+8c6b,U+8dcc,U+8e48,U+8eba,U+8f9c,U+8fa3,U+8fa8,U+8fb0-8fb1,U+8fc4,U+9006,U+901d,U+9022,U+903c,U+9042,U+904f,U+9063,U+90a2,U+90b1,U+90b5,U+9102,U+916c,U+9177,U+9187,U+9189,U+961c,U+964b,U+96a7,U+96c1,U+970d,U+971c,U+9738,U+978d,U+97ad,U+9b3c,U+9b41-9b42,U+9b44-9b45,U+9b4f,U+9b54,U+9e7f,U+9e9f,U+9f0e,U+9f3b;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_82438c.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4fc1,U+5000,U+5096,U+50c9,U+5114-5115,U+526e,U+5274,U+5399,U+53c4,U+55f6,U+5635,U+5638,U+565d,U+5665,U+566f,U+56c0,U+56c8,U+57b5,U+5816,U+581d,U+584f,U+58d9,U+58e2,U+5b00,U+5b19,U+5b21,U+5b2a,U+5d0d,U+5d33,U+5e43,U+5ee1,U+5ee9,U+614d,U+616a,U+6173,U+61e8,U+6261,U+6331,U+645c,U+6476,U+64fc,U+651b,U+6894,U+6898,U+6a05,U+6a34,U+6a81,U+6a89,U+6abe,U+6add-6ade,U+6ae8,U+6aec,U+6af8,U+6b0f,U+6b5f,U+6b7f,U+6ba4,U+6c0c,U+6de5,U+6f35,U+6f7f,U+6fae,U+7026,U+7032,U+7162,U+716c,U+733b,U+7341,U+743a,U+74a6,U+74d4,U+74da,U+7646,U+7658,U+7669,U+766d,U+7670,U+7725,U+7864,U+7868,U+78b8,U+78e7,U+7906,U+7a61,U+7b67,U+7bcb,U+7bf3,U+7c00,U+7c1e,U+7cf2,U+7d08,U+7d3c,U+7d3f,U+7d4e,U+7d5b,U+7d5d,U+7d83,U+7d9e,U+7da3,U+7db0,U+7db9,U+7dc7,U+7de1,U+7df1,U+7e0a,U+7e1e-7e1f,U+7e35,U+7e7e,U+7e98,U+7fb6,U+802c,U+8141,U+8278,U+8407,U+8435,U+8494,U+84e7,U+84ef,U+84f4,U+84fd,U+8541,U+8555,U+85ce,U+85ea,U+87bb,U+87f6,U+889e,U+8a0c,U+8a10,U+8a4e,U+8a58,U+8a6c,U+8a7f,U+8a86,U+8a91,U+8a9a,U+8ab6,U+8ac2,U+8acf,U+8adb,U+8ade,U+8ae1-8ae2,U+8ae4,U+8afc,U+8b04-8b05,U+8b59,U+8b92,U+8b95-8b96,U+8b9c,U+8c9b,U+8cb2-8cb3,U+8cba,U+8cfb,U+8cfe,U+8d04,U+8e55,U+8e91,U+8e95,U+8edb,U+8eeb,U+8f1c,U+8f1e,U+8f26,U+8f33,U+8f54,U+9015,U+9090,U+90df,U+90e4,U+9134,U+9136,U+91c5,U+91d3-91d5,U+91e4,U+91e7,U+91f7,U+9204,U+9227,U+9230,U+9239,U+923f,U+9248,U+9266,U+926d,U+927a,U+927f,U+9283,U+92a0,U+92a3,U+92a6,U+92aa-92ab,U+92e8,U+92ee,U+92f1,U+9319,U+931b,U+9338,U+9340,U+93a6,U+93a9,U+93c3,U+93cc-93cd,U+93e8,U+9403,U+9413,U+9419,U+943f,U+944c,U+9454,U+946d,U+9477,U+9582,U+958e,U+95b6,U+95b9,U+95bc,U+95be,U+95c3,U+95c8,U+95cb,U+95d0,U+9768,U+97c9,U+97d9,U+9807,U+980e,U+9826,U+984e,U+9853,U+9873-9874,U+98ae,U+98bc,U+98e0,U+98ed,U+9908,U+9949,U+99d1,U+99d9,U+99e2,U+9a05,U+9a43-9a44,U+9ad5,U+9b29,U+9b2e,U+9b4e,U+9b58,U+9b74,U+9b8e,U+9b90,U+9bab,U+9bc0,U+9bc7,U+9bd6-9bd7,U+9be1-9be2,U+9bff,U+9c09,U+9c13,U+9c23,U+9c33,U+9c45,U+9ce7,U+9d07,U+9d52,U+9dbb,U+9dc0,U+9dc2,U+9df3,U+9df8,U+9e15,U+9e1d,U+9efd,U+9eff,U+9f5c,U+9f66,U+9f6a,U+9f76-9f77;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_2c89c1.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+37e,U+384-38a,U+38c,U+38e-3a1,U+3a3-3ce,U+3d1-3d2,U+3d6,U+400-45f,U+462-463,U+46b,U+472-475,U+490-493,U+497,U+49a-49d,U+4a2-4a3,U+4ae-4b3,U+4b8-4bb,U+4ca,U+4d8-4d9,U+4e8-4e9,U+1f45,U+54f3,U+5515,U+5adc,U+5e58,U+5fee,U+6499,U+6516,U+6bff,U+6d5e,U+7020,U+7044,U+75b0,U+7603,U+799a,U+7e70,U+8014,U+80d9,U+8159,U+8183,U+81a3,U+81aa,U+8464,U+8811,U+89dc,U+8a92,U+8b56,U+8cb0,U+8cd5,U+8ce7,U+8e93,U+8ea6,U+8fee,U+9139,U+93aa,U+940b,U+95bf,U+986c,U+9a2d,U+9a38,U+9beb,U+9bf4,U+9c20,U+9c37,U+9d1d,U+9e88,U+9f47,U+9f60;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_bee5fd.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+50af,U+50c2,U+5108,U+5137,U+513b,U+528a,U+5504,U+559e,U+560d,U+561c,U+5660,U+5680,U+5694-5695,U+56a8,U+56b6,U+56c1,U+5707,U+5770,U+57e1,U+580a,U+584b,U+58ce,U+58e0,U+5950,U+597c,U+5aa7,U+5ad7,U+5b08,U+5b0b,U+5b76,U+5b7f,U+5cf4,U+5da0,U+5da7,U+5db4,U+5dcb,U+5ec4,U+60f2,U+60fb,U+6134,U+613e,U+6142,U+615f,U+616b,U+61cc,U+61e3,U+61fa,U+6229,U+636b,U+64a3,U+64b3,U+64bb,U+64c4,U+64ef,U+6523,U+66b1,U+66c7,U+66d6,U+689f,U+690f,U+69e8,U+6a9c,U+6aa3,U+6ad3,U+6adf,U+6aea,U+6af1,U+6b1e,U+6b9e,U+6bae,U+6c2c,U+6c33,U+6c39,U+6df6,U+6f1a,U+6fa0,U+7005,U+7028,U+7043,U+7069,U+7146,U+71dc,U+71f4,U+7296,U+72fd,U+7319,U+7370,U+7449,U+74a3,U+75d9,U+75fe,U+763b,U+765f,U+7664,U+76b0,U+77bc,U+78ad,U+78e3,U+7931,U+7a1f,U+7a4c,U+7c11,U+7c23,U+7c5f,U+7d02,U+7d13,U+7d15,U+7d31,U+7d3a,U+7d40,U+7dbe,U+7dcb,U+7dd7-7dd8,U+7df2,U+7e09,U+7e39,U+7e48,U+7e88,U+7fa5,U+8075,U+81cf,U+81d2,U+82bb,U+83a2,U+83a7,U+858c,U+8594,U+85ba,U+8604,U+861a,U+866f,U+8706,U+8716,U+875f,U+8768,U+8784,U+87c4,U+87c8,U+87ce,U+8814,U+8823,U+8831,U+8938,U+893b,U+8960,U+8964,U+8998,U+89a6,U+89ac,U+89b2,U+89f4,U+8a03,U+8a15-8a16,U+8a36,U+8a41,U+8a46,U+8a52,U+8a54,U+8a5b,U+8a61,U+8a70,U+8a85,U+8aa5,U+8acd,U+8aeb,U+8aed,U+8af6,U+8b14,U+8b16,U+8b2b,U+8b3e,U+8b4e,U+8cc5,U+8cda,U+8e4c,U+8e89,U+8e9a,U+8ea1,U+8ea5,U+8eaa,U+8ed4,U+8ef2,U+8efb,U+8f42,U+8f64,U+9106,U+9148,U+91d2,U+91f5,U+91f9,U+9200,U+9210-9211,U+922e,U+9238,U+924d,U+925e,U+9278,U+92bc,U+92c3,U+92c7,U+92e5,U+92ef,U+9315,U+9341,U+9347,U+9354,U+9358,U+9398,U+93a3,U+93a7,U+93b0,U+93b5,U+93d1,U+93dc,U+93e4,U+93f5,U+9410,U+9427,U+943a,U+9463,U+9481,U+95ac-95ad,U+95de,U+9658,U+973d,U+9742,U+9744,U+97c3,U+97de,U+981c,U+9821,U+9837,U+9865,U+9870,U+98af,U+98b6,U+98c6,U+98c8,U+98e9,U+98f4,U+9909,U+9911,U+991b,U+991e,U+993f,U+9948,U+994c,U+9962,U+9a0f,U+9a16,U+9a2e,U+9a41,U+9a64,U+9acf,U+9ad6,U+9bad,U+9bca,U+9bdb,U+9be4,U+9be7,U+9bf0,U+9bfd,U+9c0d,U+9c25,U+9c2d,U+9c31,U+9c48,U+9c52,U+9c56,U+9c78,U+9ce9,U+9cf6,U+9d06,U+9d23,U+9d61,U+9d6a,U+9d89,U+9d9a,U+9dd3,U+9df2,U+9e1a-9e1b,U+9e85,U+9ea9,U+9f5f,U+9f67,U+9f6c,U+9f95;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_9ad5e8.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4e99,U+4f47,U+50e5,U+5102,U+513a,U+513c,U+5157,U+5191,U+5284,U+54e2,U+55b2,U+55c6-55c7,U+55da,U+55e9,U+5616,U+562e,U+5630,U+5653,U+56ea,U+579c,U+585a,U+5879,U+58ae,U+59cd,U+5acb,U+5af5,U+5afa,U+5b38,U+5c5c,U+5d22,U+5d87,U+5d97,U+5db8,U+5dd2,U+5dd4,U+5e40,U+5edd,U+60b5,U+611c,U+6137,U+615a,U+619a,U+61ab,U+633e,U+6384,U+6425,U+6440,U+645f,U+6473,U+649a,U+64f0,U+64f7,U+64fb,U+6506,U+6519,U+6582,U+6595,U+66c4,U+67f5,U+68e7,U+69f3,U+6a62,U+6ab3,U+6ab8,U+6abb,U+6ada-6adb,U+6b12,U+6bab,U+6c08,U+6d79,U+6e19,U+6ece,U+6ef7-6ef8,U+6f2c,U+6f41,U+6f6f,U+6f97,U+6fd8,U+6ffa,U+705d,U+70f4,U+71c9,U+71fc,U+71fe,U+7258,U+729b,U+72a2,U+7377,U+737a,U+737c,U+73a8,U+743f,U+744b,U+7489,U+74bd,U+74bf,U+74cf,U+750c,U+7515,U+760d,U+7627,U+7647,U+7662,U+766c,U+7672,U+769a,U+7787,U+792a-792c,U+798e,U+79b1,U+79c8,U+7b8b,U+7be4,U+7c0d,U+7c2b,U+7c37,U+7c6c,U+7c6e,U+7cc9,U+7d1c,U+7d33,U+7d46,U+7d73,U+7dac,U+7dde,U+7e08,U+7e10,U+7e1d,U+7e43,U+7e45,U+7e6e,U+7e93,U+7f4c,U+7f88,U+7ffa,U+811b,U+81be-81bf,U+81cd,U+82e7,U+8396,U+83f4,U+8477,U+849e,U+84c0,U+854e,U+858a,U+85f9-85fa,U+8622,U+865c,U+86fb,U+8778,U+87a2,U+880d,U+884a,U+8956,U+89b7,U+8a1b,U+8a25,U+8a6b,U+8a6d-8a6e,U+8a7c,U+8ab9,U+8ac9,U+8adc,U+8ae6,U+8af3,U+8afa,U+8b01,U+8b10,U+8b17,U+8b28,U+8b2c,U+8b33,U+8b4f,U+8cc1,U+8cd2,U+8ce1,U+8d05,U+8d0d,U+8d16-8d17,U+8e63,U+8e7a,U+8e8a,U+8efc,U+8efe,U+8f12,U+8f25,U+8f45-8f46,U+8fae,U+902c,U+9114,U+9116,U+913a,U+9183,U+91c1,U+91e9,U+91fa,U+9208,U+920d,U+9226,U+9237,U+923e,U+9249,U+9251,U+9257,U+925a,U+9262,U+926c,U+927b,U+9291,U+9293,U+9296,U+92a5,U+92ac,U+92bb,U+92cc,U+92e4,U+92f0,U+9310,U+932e,U+9333,U+9365,U+936c,U+9376,U+937a,U+93a2,U+93ac,U+93b3,U+93d7-93d8,U+93de,U+93e2,U+942b,U+942e,U+9432,U+9460,U+947e,U+9586,U+958f,U+95a1,U+95a8,U+95cc,U+95d4-95d5,U+975a,U+9766,U+9830,U+9839,U+990c,U+9912,U+9921,U+993e,U+9943,U+9957,U+995e,U+99b1,U+99b4,U+99df,U+99ed,U+9a2b,U+9a3e,U+9a40,U+9a4d,U+9a5b,U+9a65,U+9a6a,U+9aaf,U+9b22,U+9b77,U+9b81,U+9bc9,U+9be8,U+9c54,U+9c58,U+9d15,U+9d1b,U+9d26,U+9d51,U+9d60,U+9d72,U+9da9,U+9daf,U+9dd7,U+9dfa,U+9e1e,U+9f72;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_c2b336.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4f96,U+4fb6,U+4fe0,U+5098,U+50ad,U+50d5,U+510d,U+516a,U+51dc,U+522a,U+52fb,U+5331,U+5379,U+53ad,U+5436,U+555e,U+5614,U+562f,U+5687,U+56a5,U+56c2,U+56d1,U+582f,U+5862,U+589c,U+58b3,U+58fa,U+59e6,U+5a41,U+5a6d,U+5b0c,U+5be2,U+5bf5,U+5c37,U+5c4d,U+5ce9,U+5dbc,U+5de3,U+5e57,U+5eec,U+5f5e,U+5fa0,U+6065,U+60b6,U+60bd,U+60f1,U+6144,U+618a,U+6190,U+61f6,U+61fc,U+61fe,U+63c0,U+6417,U+646f,U+647b,U+6493,U+64be-64bf,U+64f1-64f2,U+650f,U+6522,U+652a,U+6583,U+65ac,U+665d,U+6688-6689,U+66e0,U+66ec,U+6727,U+68f2,U+6953,U+6968,U+6a3a,U+6ae5,U+6afb,U+6b16,U+6bad,U+6baf,U+6bb2,U+6bc6,U+6c2b,U+6cdd,U+6d36,U+6d87,U+6dea,U+6df5,U+6e26,U+6e4a,U+6f23,U+6f70,U+6f80,U+6fc1,U+6ff0,U+6ffe,U+7006,U+7009,U+700f,U+7018,U+701d,U+7055,U+7064,U+7152,U+7192,U+71be,U+71c1,U+71d9,U+71ed,U+71fb,U+720d,U+7463,U+7469,U+746f,U+7587,U+758a,U+760b,U+7613,U+7621,U+7661,U+766e,U+7671,U+7681,U+76ba,U+76de,U+775c,U+779e,U+77ef,U+786f,U+78ef,U+79aa,U+79bf,U+7a08,U+7a1c,U+7a62,U+7aaa,U+7aba,U+7ac4-7ac5,U+7ac7,U+7b4d,U+7b8f,U+7be9,U+7c3e,U+7cde,U+7d09,U+7d43,U+7d5e,U+7d62,U+7d79,U+7d81,U+7d8f,U+7d91,U+7db4,U+7db8,U+7dba-7dbb,U+7dbd,U+7ddd,U+7def,U+7e37,U+7e46,U+7e55,U+7e5a,U+7e6d,U+7e79,U+7e7d,U+7e8f,U+7f75,U+7fa8,U+7ff9,U+8073,U+807e,U+8123,U+814e,U+81a9,U+834a,U+8466,U+8525,U+852d,U+8553,U+856a,U+8588,U+863f,U+879e,U+87ec,U+87fb,U+8805,U+881f,U+883b,U+896a,U+8993,U+8a1d,U+8a23,U+8a60,U+8a63,U+8aa1,U+8aa3,U+8aa6,U+8aa8,U+8ac4,U+8ad2,U+8af1,U+8af7,U+8b0a,U+8b0e,U+8b20,U+8b41,U+8b74,U+8b8e,U+8c4e,U+8c8d,U+8c9e,U+8caf,U+8cb6,U+8cbd,U+8cca,U+8cd1,U+8cdc,U+8ce4,U+8cec,U+8d13,U+8e34,U+8ec0,U+8ed2,U+8f1f,U+8f3e,U+8f4d,U+905c-905d,U+9087,U+9132,U+919e,U+91ac,U+91d7-91d8,U+9209,U+9215,U+921e,U+9223,U+923a,U+9240,U+92a8,U+92c5,U+92f8,U+9318,U+931a,U+9320,U+9328,U+934d,U+9382,U+938a,U+93df,U+93fd,U+9433,U+9438,U+9470,U+9472,U+947c,U+947f,U+9594,U+95a5,U+9695,U+96b4,U+96b8,U+96cb,U+96db,U+9711,U+97cc,U+97dc,U+9824,U+985b,U+986b,U+9871,U+98e2,U+98ea,U+9903,U+9913,U+991a,U+9935,U+993b,U+9945,U+99ad,U+99c1,U+99d2,U+99dd,U+99f1,U+99ff,U+9a01,U+9a4a,U+9a62,U+9bd4,U+9c32,U+9c3b,U+9c49,U+9c57,U+9c77,U+9d09,U+9d3f,U+9ef4,U+9f4b,U+9f9c;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_aacef2.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4e1f,U+5006,U+5009,U+5074-5075,U+5078,U+50f1,U+5109,U+5147,U+514c,U+51cd,U+51f1,U+524e,U+525d,U+5270,U+528d,U+52f3,U+52f8,U+53b2,U+53e2,U+5442,U+5451,U+5586,U+559a,U+55aa,U+5606,U+5617,U+5674,U+5875,U+588a,U+58be,U+58d8,U+58df,U+58e9,U+593e,U+599d,U+5b30,U+5c5b,U+5c62,U+5ca1,U+5d50,U+5d84,U+5dba,U+5dbd,U+5dd6,U+5e25,U+5e5f,U+5ec1-5ec2,U+5eda,U+5edf,U+5f14,U+5f4c,U+5f4e,U+5f65,U+6046,U+6085,U+6158,U+617c,U+61a4,U+61b6,U+61c7,U+61f2,U+61f8,U+6200,U+62cb,U+62dd,U+6399,U+6488,U+6490,U+64ab,U+64b2,U+64cb,U+64e0-64e1,U+64ec,U+64fe,U+6514,U+651c,U+652c,U+6558,U+6575,U+6736,U+687f,U+68d7,U+68df,U+69d3,U+6a01,U+6a1e,U+6a38,U+6ac3,U+6b04,U+6b3d,U+6bbc,U+6bc0,U+6d29,U+6dbc,U+6de9,U+6dfa,U+6e3e,U+6e6f,U+6ebc,U+6ec4,U+6ecc,U+6eec,U+6eef,U+6ef2,U+6f11,U+6f3f,U+6f51,U+6f86-6f87,U+6fb1,U+6feb,U+7015,U+701f,U+703e,U+7051,U+7058,U+7165,U+7169,U+71e6,U+721b,U+723a,U+727d,U+72a7,U+72f9,U+7336,U+7343-7345,U+7375,U+7378,U+7464,U+746a,U+74ca,U+7501,U+775e,U+77da,U+78a9,U+78da,U+797f,U+798d,U+79a6,U+7a4e,U+7aa9,U+7aaf,U+7ac8,U+7aca,U+7c60,U+7cb5,U+7d0b,U+7d17,U+7d68,U+7da2,U+7db1,U+7dbf,U+7dd2,U+7de0,U+7de3,U+7dec,U+7e1b,U+7e2b,U+7e61,U+7e69,U+7e96,U+7e9c,U+7f77,U+8070,U+8076,U+8105,U+8108,U+816b,U+8178,U+819a,U+81d8,U+81e5,U+8259,U+8266,U+8332,U+8490,U+84bc,U+84ee,U+8514,U+8523,U+8569,U+856d,U+8591,U+85a6,U+8606,U+860a-860b,U+8755,U+8766,U+8836,U+8932,U+896f,U+8972,U+89d4,U+8a1f,U+8a50,U+8a73,U+8a87,U+8a98,U+8ae7,U+8b19,U+8b39,U+8b5a,U+8b5c,U+8b5f,U+8b6f,U+8c48,U+8c54,U+8c93,U+8caa,U+8cc2-8cc4,U+8cc8,U+8ce2,U+8ce6,U+8ced,U+8cf4,U+8d1b,U+8e30,U+8e64,U+8e8b,U+8ecb,U+8ef8,U+8f13-8f14,U+8f29,U+8f3b,U+8f3f,U+8f5f,U+8faf,U+9059,U+908f,U+9112,U+919c,U+91c0,U+91d0,U+91e3,U+9214,U+9234,U+925b,U+9264,U+9298,U+929c,U+92b3,U+92c1,U+934b,U+935b,U+93c7-93c8,U+9444,U+9583,U+9592,U+9598,U+95a3,U+95a9,U+95b1,U+95bb,U+95c7,U+95d6,U+95e1,U+9670,U+96b1,U+9727,U+978f,U+97cb,U+97fb,U+9801,U+9803,U+980c,U+9811,U+9838,U+9846,U+984f,U+98b3,U+98c4,U+98fc,U+9905,U+992c,U+994b,U+9952,U+99ae,U+99b3,U+99d5,U+9a0e,U+9a37,U+9a45,U+9a55,U+9a5f,U+9ad2,U+9b27-9b28,U+9b31,U+9b91,U+9ce5,U+9cf4,U+9d28,U+9d3b,U+9d5d,U+9db4,U+9df9,U+9e79,U+9e7c,U+9f21,U+9f52,U+9f90,U+9f94;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_0ff13d.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4e82,U+5016,U+502b,U+5049,U+5091,U+50b5,U+50b7,U+50be,U+50d1,U+50de,U+5100-5101,U+511f,U+5132,U+518a,U+525b,U+5291,U+52c1,U+52f5,U+5433,U+5553,U+55ac,U+55ce,U+570f,U+5713,U+584a,U+58c7,U+58d3,U+58de,U+58ef,U+58fd,U+5920,U+5922,U+5967,U+596a,U+596e,U+5a1b,U+5a66,U+5abd,U+5b43,U+5b6b,U+5bae,U+5bec,U+5c0b,U+5cef,U+5cf6,U+5cfd,U+5d11,U+5d17,U+5e33,U+5e63,U+5e6b,U+5eab,U+5ec8,U+5ee2,U+5ef3,U+5f48,U+5f7f,U+5f91,U+5fb9,U+60e1,U+6163,U+616e,U+6176,U+617e,U+6182,U+6191,U+61b2,U+61f7,U+6232,U+6368,U+6372,U+6383,U+639b,U+63da,U+63f9,U+6416,U+6436,U+64a5,U+64c1,U+64c7,U+64fa,U+651d,U+6524,U+6557,U+65c2,U+6649,U+66a2,U+66ab,U+66c9,U+68c4,U+68ca,U+694a,U+69cd,U+69ea,U+6a11,U+6a13,U+6a4b,U+6a6b,U+6a94,U+6b61,U+6b78,U+6b98,U+6bba,U+6c59,U+6dda,U+6de8,U+6e09,U+6e67,U+6e9d,U+6eab,U+6ec5,U+6efe,U+6f01,U+6f22,U+6f32,U+6f38,U+6f54,U+6f5b,U+6f64,U+6fc3,U+6fe4,U+6ff1,U+700b,U+7063,U+707d,U+70cf,U+7149,U+7159,U+71c8,U+71d2,U+7210,U+7240,U+7246,U+7274,U+755d,U+7562,U+7570,U+75e0,U+7642,U+7652,U+7665,U+76c3,U+76dc,U+76e4,U+76e7,U+7843,U+78bc,U+7919,U+791f,U+7955,U+79ae,U+7a40,U+7aae,U+7b46,U+7bc9,U+7c21,U+7c43,U+7c4c,U+7c64,U+7cfe,U+7d10,U+7d14,U+7d19,U+7d1b,U+7d21,U+7d2e,U+7d30,U+7d39,U+7d55,U+7d61,U+7d72,U+7da0,U+7db5,U+7de9,U+7df4,U+7e2e,U+7e31,U+7e3e,U+7e5e,U+7e6a,U+7e73,U+7e8d,U+7f70,U+7feb,U+8056,U+8085,U+812b,U+8139,U+8166,U+8173,U+81a0,U+81bd,U+81c9,U+820a,U+8271,U+838a,U+840a,U+8449,U+84cb,U+85a9,U+85cd,U+865b,U+8667,U+87f2,U+885d,U+89ba,U+89bd,U+89f8,U+8a02,U+8a34,U+8a3a,U+8a5e,U+8a62,U+8a69,U+8a95,U+8a9e,U+8aa0,U+8aa4,U+8ab0,U+8ab2,U+8abc,U+8aee,U+8af8,U+8afe,U+8b00,U+8b02,U+8b1d,U+8b7d,U+8c6c,U+8c9d,U+8ca2,U+8ca9,U+8cab,U+8cb4,U+8cbc,U+8cc0,U+8cd3,U+8cde,U+8ce0,U+8cfa,U+8d08,U+8d0a,U+8d0f,U+8d95,U+8d99,U+8da8,U+8de1,U+8e10,U+8e8d,U+8ecc,U+8edf,U+8f09,U+8f1b,U+8f1d,U+8f2a,U+8f2f,U+8f44,U+8f4e,U+8fad,U+9055,U+905e,U+9072,U+9077,U+907a,U+907c,U+9081,U+90f5,U+9127,U+912d,U+9130,U+91cb,U+91e6,U+9285,U+92d2,U+92ea,U+9304,U+9326,U+932b,U+932f,U+9375,U+937e,U+9396,U+93e1,U+9451,U+947d,U+9589,U+95c6,U+95ca,U+95e2,U+965d,U+9663,U+9678,U+96d6,U+96dc,U+96de,U+9748,U+975c,U+97a6,U+97d3,U+9802,U+9806,U+9812-9813,U+9817,U+983b,U+98ef,U+98f2,U+98fd-98fe,U+99d0,U+99db,U+9a19,U+9a30,U+9a5a,U+9b06,U+9b5a,U+9b6f,U+9bae,U+9cf3,U+9d6c,U+9e7d,U+9e97,U+9ea5,U+9f15,U+9f4a,U+9f61;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_00397b.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4e26,U+4e9e,U+4f15,U+4f48,U+4f54,U+4f86,U+4fb7,U+4fec,U+500b,U+5011,U+5099,U+50a2,U+50b3,U+50c5,U+50f9,U+5104,U+512a,U+5152,U+5167,U+5169,U+5225,U+5247,U+524b,U+5275,U+5283,U+5287,U+5289,U+52d5,U+52d9,U+52dd-52de,U+52e2,U+532f,U+5340,U+5354,U+537b,U+537d,U+53c3,U+54e1,U+5538,U+554f,U+55ab,U+55ae,U+5678,U+56b4,U+570b,U+570d,U+5712,U+5716,U+5718,U+57f7,U+5805,U+5831,U+5834,U+59b3,U+5b78,U+5be6-5be7,U+5be9,U+5beb,U+5bf6,U+5c07-5c08,U+5c0d-5c0e,U+5c46,U+5c64,U+5c6c,U+5e2b,U+5e36,U+5e79,U+5e7e,U+5ee3,U+5f35,U+5f37,U+5f9e,U+5fa9,U+611b,U+614b,U+61c9,U+6230,U+6236,U+63db,U+63ee,U+640d,U+64ca,U+64d4,U+64da,U+64f4,U+654e,U+6578,U+65b7,U+65e3,U+6642,U+66f8,U+6703,U+6771,U+67fb,U+689d,U+696d,U+6975,U+69ae,U+69cb,U+6a02,U+6a19,U+6a23,U+6a39,U+6a5f,U+6aa2,U+6b0a,U+6b50,U+6b72,U+6b77,U+6c23,U+6c7a,U+6c92,U+6cc1,U+6e1b,U+6e2c,U+6e96,U+6eff,U+6fa4,U+6fdf,U+7121,U+71b1,U+71df,U+722d,U+7232,U+723e,U+7260,U+72c0,U+734e,U+7368,U+7372,U+737b,U+73fe,U+74b0,U+7522,U+756b,U+7576,U+767c,U+76e1,U+76e3,U+771e,U+774f,U+77ad,U+78ba,U+790e,U+7926,U+7a05,U+7a2e,U+7a31,U+7a4d,U+7a69,U+7af6,U+7bc0,U+7bc4,U+7ce7,U+7d00,U+7d04-7d05,U+7d0d,U+7d1a,U+7d42,U+7d44,U+7d50,U+7d66,U+7d71,U+7d93,U+7d9c,U+7dab,U+7dad,U+7db2,U+7dca,U+7de8,U+7dfb,U+7e23,U+7e3d,U+7e54,U+7e7c,U+7e8c,U+7e94,U+7f85,U+7fa3,U+7fa9,U+7fd2,U+805e,U+806f,U+8072,U+8077,U+807d,U+8129,U+81e8,U+8207-8209,U+83d3,U+83ef,U+842c,U+84c6,U+85dd,U+85e5,U+8607,U+862d,U+8655,U+865f,U+8846,U+8853,U+8855,U+885b,U+88cf,U+88dc-88dd,U+88fd,U+8988,U+898b,U+898f,U+8996,U+89aa,U+89c0,U+8a08,U+8a0a,U+8a0e,U+8a13,U+8a17-8a18,U+8a2a,U+8a2d,U+8a31,U+8a3b,U+8a55,U+8a66,U+8a71-8a72,U+8a8c-8a8d,U+8aaa,U+8abf,U+8ac7,U+8acb,U+8ad6,U+8b1b,U+8b49,U+8b58,U+8b70,U+8b77,U+8b80,U+8b8a,U+8b93,U+8c50,U+8ca0-8ca1,U+8ca7-8ca8,U+8cac,U+8cb7-8cb8,U+8cbb,U+8cbf,U+8cc7,U+8ce3,U+8cea,U+8cfc-8cfd,U+8eca,U+8ecd,U+8f03,U+8f15,U+8f38,U+8f49,U+8fa6,U+8fb2,U+8ff4,U+9019,U+9023,U+9032,U+904a-904b,U+904e,U+9054,U+9060,U+9069,U+9078,U+9084,U+908a,U+9109,U+91ab,U+91dd,U+9245,U+9280,U+92b7,U+92fc,U+9322,U+9336,U+93ae,U+9435,U+9577,U+9580,U+958b,U+9593,U+95a4,U+95dc,U+9673,U+967d,U+968a,U+968e,U+969b,U+96a8,U+96aa,U+96bb,U+96d9,U+96e2-96e3,U+96f2,U+96fb,U+97c6,U+97ff,U+9805,U+9808,U+9810,U+9818,U+982d,U+984c-984d,U+9858,U+985e,U+9867,U+986f,U+98a8,U+98db,U+990a,U+9918,U+9928,U+99ac,U+9a57,U+9ad4,U+9b25,U+9ebc,U+9ec3,U+9ed2,U+9ede,U+9ee8,U+9f63,U+9f8d;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_e7daea.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4f25,U+4f27,U+4f65,U+4fa9-4faa,U+4fe3,U+4fe6,U+50a7,U+51eb,U+51fc,U+523d,U+5240,U+5250,U+538d,U+53c1,U+5452-5453,U+5456,U+549d,U+54d3-54d4,U+54dd,U+5527,U+556d-556e,U+55eb,U+55f3,U+5624,U+568f,U+56af,U+5739,U+575c,U+57b2,U+57b4,U+57da,U+57ef,U+5942,U+59ab,U+5aaa,U+5ad2,U+5ad4,U+5af1,U+5c98,U+5d03,U+5d5b,U+5d5d,U+5def,U+5e0f,U+5e91,U+5eea,U+5ffe,U+6004,U+6079,U+60ab,U+60ad,U+6120,U+61b7,U+6217,U+622c,U+6266,U+629f,U+6332,U+63b8,U+63bc,U+64b8,U+64ba,U+66a7,U+679e,U+67a7,U+67b3,U+67fd,U+6800,U+680c,U+6860,U+68c2,U+691f,U+6924,U+6987,U+6989,U+6a2f,U+6a79,U+6a7c,U+6aa9,U+6b24,U+6b81,U+6b87,U+6b92-6b93,U+6bc2,U+6c07,U+6c29,U+6ca9,U+6cf6,U+6d4d,U+6da0,U+6e0c,U+6e86,U+6edf,U+6f4b,U+6f74,U+6fd1,U+7080,U+7145,U+7198,U+72b8,U+72f2,U+732c,U+737e,U+73d0,U+7477,U+748e,U+74d2,U+7596,U+75a0,U+75c8,U+75e8,U+763f,U+765e,U+7726,U+7817,U+781c,U+7856,U+7877,U+789b,U+7a51,U+7b15,U+7b5a,U+7ba6-7ba7,U+7baa,U+7c74,U+7c9c-7c9d,U+7ccd,U+7e9f,U+7ea8,U+7ec0,U+7ecb,U+7ed0,U+7ed4,U+7ed7,U+7ee1,U+7ee6,U+7ee8,U+7ef1,U+7efa-7efb,U+7efe,U+7f01,U+7f0d,U+7f11,U+7f17,U+7f1b,U+7f1f,U+7f22,U+7f26,U+7f31,U+7f35,U+8027,U+80fc,U+816d,U+81bb,U+8279,U+82c1,U+82cc,U+82d8,U+8315,U+835c,U+8366,U+8368-8369,U+839c,U+83b3-83b4,U+83b8,U+83bc,U+84d1,U+84e0,U+85ae,U+85c1,U+85d3,U+86cf,U+86d4,U+86f3-86f4,U+8748,U+877c,U+886e,U+88e2-88e3,U+89c7,U+89de,U+8a8a,U+8ba6-8ba7,U+8bb5,U+8bcc,U+8bce,U+8bd3,U+8bd6,U+8bdf,U+8be8,U+8bee,U+8bf3,U+8bf9,U+8c00,U+8c04,U+8c07,U+8c14,U+8c16-8c17,U+8c1d,U+8c20-8c21,U+8c25,U+8c2f-8c30,U+8c36,U+8d30,U+8d36,U+8d3d,U+8d40,U+8d59,U+8d5c,U+8dde,U+8df8,U+8e2f,U+8e51,U+8e70,U+8f6b,U+8f6d,U+8f73,U+8f78,U+8f87,U+8f8b,U+8f8e-8f8f,U+8f94,U+8f9a,U+8ff3,U+9026,U+90ba,U+90c4,U+90cf-90d0,U+917d,U+933e,U+9486-9487,U+948c-948d,U+948f-9490,U+94a3,U+94aa-94ab,U+94ad,U+94af,U+94b2,U+94ba,U+94bd,U+94bf,U+94c8,U+94ca,U+94cc-94cd,U+94d1-94d2,U+94d5-94d6,U+94d9,U+94df,U+94e9-94ea,U+94ef-94f0,U+94f3,U+94f7,U+94fc-94fd,U+9507,U+9515,U+951b,U+951d,U+9531,U+9546,U+954a,U+954f,U+9554,U+9558-9559,U+955d-955e,U+9566-9567,U+956b,U+9571-9572,U+95e9,U+95f1,U+95f3,U+9604,U+9608-960b,U+960f,U+9612,U+9615,U+9617,U+9765,U+97af,U+97ea,U+9878,U+987c,U+9880,U+988f,U+989a-989b,U+989e,U+98a2,U+98a7,U+98d1,U+98d5,U+9963,U+996c,U+9980,U+998a,U+9991,U+9994-9995,U+9a78,U+9a7d,U+9a88,U+9a93,U+9a9b,U+9aa0,U+9aa2,U+9ac5,U+9acc,U+9b47,U+9b49,U+9c82,U+9c87,U+9c90-9c91,U+9c9b,U+9c9e,U+9ca5,U+9ca7,U+9ca9,U+9cad-9cae,U+9cb1,U+9cb5,U+9cc3,U+9cc7,U+9cca,U+9cce-9ccf,U+9cd3,U+9cd5,U+9cd9,U+9e28-9e29,U+9e2c,U+9e37,U+9e41-9e42,U+9e46-9e47,U+9e58,U+9e5a,U+9e5e,U+9e6c,U+9efe,U+9f0b,U+9f39,U+9f83,U+9f85,U+9f87-9f8a,U+9f8c;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_269445.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4e2c,U+4f1b,U+507e,U+5181,U+522d,U+523f,U+52a2,U+5326,U+537a,U+53a3,U+5423,U+5459,U+54b4,U+54d5,U+54d9,U+54dc,U+5523,U+55be,U+56df,U+5786,U+57d8,U+5941,U+5a08,U+5b37,U+5c66,U+5cbd,U+5d74,U+5e31,U+5e3b,U+5e42,U+5e5e,U+5f11,U+5f2a,U+6003,U+603c,U+610d,U+6126,U+61d4,U+6206,U+620b,U+6322,U+63b4,U+63f8,U+6445,U+6484,U+6769,U+67a8,U+680a,U+684a,U+6861,U+6864,U+6920,U+69e0,U+6a65,U+6bf5,U+6cf7,U+6cfa,U+6d48,U+6e16,U+6ed7,U+6ee0,U+6f24,U+6f46,U+709d,U+7173,U+717a,U+72ad,U+72ef,U+7303,U+7321,U+75ac,U+75d6,U+7605,U+7617,U+76b2,U+770d,U+7857,U+78b9,U+7962,U+7a06,U+7aad,U+7b3e,U+7ba8,U+7c16,U+7cc1,U+7cc7,U+7d77,U+7ea1,U+7ea5,U+7ea9,U+7ec1,U+7ee0,U+7ef2,U+7f02,U+7f07,U+7f0b-7f0c,U+7f0f,U+7f12,U+7f21,U+7f23,U+7f27,U+7f2f,U+7f32-7f33,U+7f74,U+8022,U+804d,U+80b7,U+80e8,U+80ea,U+8114,U+8136,U+817d,U+8223,U+823b,U+8288,U+82c8,U+8311,U+835b,U+836c-836e,U+83b6,U+8411,U+8487,U+8489,U+848c,U+84e3,U+8539,U+866e,U+867f,U+86f1-86f2,U+877e,U+88e5,U+89cb-89cc,U+89cf,U+89ef,U+8ba0,U+8bd4,U+8bdc,U+8bf6,U+8bfc,U+8c02,U+8c18,U+8c2b,U+8c2e,U+8c33,U+8c35,U+8d33,U+8d46-8d47,U+8d4d,U+8d55,U+8db1,U+8dd6,U+8e2c,U+8e9c,U+8f75,U+8f77,U+8f79-8f7a,U+8f7e,U+8f81-8f82,U+917e,U+948b,U+9494,U+94ac,U+94b6-94b8,U+94d7-94d8,U+94de,U+94e5,U+94eb,U+94f4,U+94f9,U+950a,U+950d-950e,U+9513-9514,U+9516,U+9518,U+951e,U+9529-952c,U+9538,U+953c,U+953e-953f,U+9544-9545,U+954e,U+9561,U+9564-9565,U+9568-956a,U+956c,U+95f6,U+95fc,U+9603,U+960c-960d,U+9667,U+96e0,U+9792,U+97b2,U+97b4,U+9883,U+989f,U+98a1,U+98a5,U+98e7,U+990d,U+9967,U+9969,U+996b,U+9987,U+9993,U+9a75,U+9a7a,U+9a80,U+9a92,U+9a96,U+9a98,U+9a9f,U+9aa3,U+9c86,U+9c8b,U+9c8e,U+9c92,U+9c94-9c95,U+9c9a,U+9ca0-9ca1,U+9ca3,U+9ca6,U+9cb0,U+9cb4,U+9cba,U+9cbc-9cbd,U+9cc6,U+9ccb,U+9cd0,U+9cd4,U+9cd8,U+9ce2,U+9e2b,U+9e31-9e32,U+9e36,U+9e38-9e3a,U+9e48,U+9e4b,U+9e4e,U+9e55,U+9e5b,U+9e63,U+9e68-9e6a,U+9e71,U+9e7e,U+9ebd,U+9ec9,U+9ee9-9eea,U+9f0d,U+9f44,U+9f51,U+9f80,U+9f86;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_36ad1b.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4eb5,U+4f2b,U+4fac,U+4fea,U+506c,U+507b,U+50a5,U+50a9,U+5156,U+51a2,U+520d,U+52d0,U+53a9,U+53ae,U+53fd,U+5457,U+545b,U+5494,U+5499,U+549b,U+54a4,U+54d2,U+551b,U+5520,U+5522,U+556c,U+55bd,U+55ec,U+565c,U+567c,U+56f5,U+5785,U+579b,U+57a7,U+57a9,U+57ad,U+57d9,U+57dd,U+5811,U+59a9-59aa,U+59f9,U+5a06,U+5a32,U+5a75-5a76,U+5b6a,U+5b73,U+5c49,U+5c99,U+5cbf,U+5cc4,U+5ce4-5ce5,U+5d02,U+5d58,U+5fcf,U+6002,U+6005-6006,U+603f,U+6078,U+607b,U+607d,U+607f,U+60af,U+60ec-60ee,U+61d1,U+626a,U+62a1,U+631b,U+631e,U+6376,U+637b,U+63b3,U+63ff,U+6448,U+64b5,U+64b7,U+64de,U+6619,U+6635,U+6654,U+67a5,U+67ad,U+680e,U+683e,U+6867,U+6901,U+692d,U+6988,U+69df,U+6b9a,U+6c32,U+6ca3-6ca4,U+6d43,U+6d52,U+6d54,U+6d9e,U+6e11,U+6ee2,U+704f,U+7096,U+70c3,U+70e9,U+70ec,U+7116,U+7118,U+724d,U+7266,U+728a,U+72c8,U+72cd,U+72de,U+72f0,U+736d,U+7391,U+73c9,U+73cf,U+740f,U+74ee-74ef,U+7572,U+75b1,U+75b4,U+75c9,U+7618,U+762a,U+7663,U+766f,U+7691,U+7751,U+77fe,U+7800,U+783b,U+783e,U+789c,U+7980,U+7a23,U+7b3a,U+7ba9,U+7bab,U+7bd1,U+7bd3,U+7c41,U+7c7c,U+7cbd,U+7ea3,U+7eb0,U+7ebe,U+7ec2,U+7ec9,U+7ecc,U+7edb,U+7eeb,U+7eef,U+7ef6,U+7f03-7f04,U+7f08,U+7f19,U+7f1c,U+7f25,U+7f2c,U+7f30,U+7f42,U+7f9f,U+7ff1,U+8069,U+80ae,U+80c4,U+810d,U+8113,U+817c,U+8191,U+8297,U+82cb,U+830f,U+8314,U+835a,U+835e,U+8360,U+8364-8365,U+836a,U+8385,U+8424,U+84df,U+84e5,U+8537,U+853a,U+8572,U+8616,U+864f,U+866c,U+8671,U+869d,U+86ac,U+86ca,U+86ce,U+86f0,U+8715,U+8717,U+87a8,U+8845,U+8884-8885,U+88c6,U+891b,U+8934,U+8941,U+89ca,U+89ce,U+89d0-89d1,U+8ba3,U+8baa-8bab,U+8bb9,U+8bc2-8bc3,U+8bc5,U+8bcb,U+8bcf,U+8bd2,U+8bd8,U+8bdb,U+8be0-8be1,U+8be4,U+8be9,U+8bf0,U+8c0c,U+8c0f,U+8c11-8c12,U+8c15,U+8c1f,U+8c27,U+8c29-8c2a,U+8c32,U+8d32,U+8d45,U+8d49,U+8d53,U+8d58,U+8d5d,U+8db8,U+8dc4,U+8df7,U+8df9,U+8e0c,U+8e7f,U+8e8f,U+8f71-8f72,U+8f7c,U+8f95,U+8f98,U+8fab,U+909d,U+90d3,U+90e6-90e7,U+92ae,U+9485,U+948e,U+9492,U+9495,U+9497,U+949a,U+949c-949d,U+94a1,U+94a4,U+94b4-94b5,U+94b9,U+94bc,U+94c2,U+94c4,U+94c6,U+94c9,U+94cb,U+94db,U+94e0-94e1,U+94e3-94e4,U+94e7,U+94f1,U+9502-9503,U+9506,U+9509,U+950f,U+9511-9512,U+9517,U+951f,U+9528,U+9534,U+9536-9537,U+9542,U+9549,U+9552-9553,U+9556-9557,U+9562-9563,U+956f-9570,U+9573,U+95eb,U+95f0,U+95fe,U+9602,U+9606,U+9611,U+9616,U+9619-961a,U+9649,U+96f3,U+9701,U+972d,U+9753,U+9791,U+97eb,U+9889-988a,U+988c,U+9893-9894,U+98a6,U+98d2-98d3,U+98d9-98da,U+98e8,U+9968,U+996f,U+9974,U+9977,U+997d,U+9984,U+998b,U+9990,U+9a6e,U+9a77,U+9a81,U+9a87,U+9a8a,U+9a90,U+9a9c-9a9e,U+9aa1,U+9aa7,U+9acb,U+9c85,U+9c88,U+9ca2,U+9ca8,U+9cab,U+9cb2-9cb3,U+9cb6-9cb7,U+9cc5,U+9ccd,U+9cdc-9cdd,U+9cdf,U+9e20,U+9e22,U+9e2a,U+9e2f,U+9e33,U+9e3e,U+9e44,U+9e49,U+9e4c,U+9e51,U+9e57,U+9e5c,U+9e66-9e67,U+9e6b,U+9e6d,U+9e73,U+9eb8,U+9f8b,U+9f9b;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_a04e21.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4e98,U+4ec7,U+4ed1,U+4f1e,U+4f63,U+4fa0,U+4fa3,U+4fa5,U+4fe8,U+50bb,U+50f5,U+51c4,U+51db,U+51ff,U+5220,U+5300,U+532e,U+5364,U+538c,U+5413,U+5450,U+5455,U+545c,U+548f,U+54bd,U+54d1,U+54d7,U+54df,U+5507,U+5567,U+5578,U+5618,U+56a3,U+56f1,U+5742,U+575e-5760,U+5815,U+5978,U+59d7,U+5a04-5a05,U+5a07,U+5a34,U+5ba0,U+5bdd,U+5c27,U+5c34,U+5c7f,U+5c96,U+5ce6,U+5ce8,U+5dc5,U+5e18,U+5e27,U+5e3c,U+5eb5,U+5f26,U+5f5d,U+5f95,U+601c,U+6064,U+607a,U+60e7,U+60eb,U+6123,U+6151,U+61d2,U+62a0,U+62e2-62e3,U+62e7,U+631a,U+631d,U+631f-6320,U+6342,U+6346,U+6361,U+6363,U+63b7,U+63ba,U+6400-6402,U+6405,U+6512,U+655b,U+658b,U+6593,U+65a9,U+65f7,U+663c,U+6652,U+6655-6656,U+672d,U+67ab,U+67e0,U+6805,U+6808-6809,U+6816-6817,U+6862,U+6866,U+6868,U+68f1,U+6984,U+69db,U+6a31,U+6a71,U+6a90,U+6b7c,U+6ba1,U+6bb4,U+6bd9,U+6be1,U+6c22,U+6c79,U+6c93,U+6ca5-6ca6,U+6cbe,U+6cde,U+6cf8,U+6cfb,U+6cfe,U+6d3c,U+6d4a,U+6d4f,U+6d9f,U+6da1,U+6da3,U+6da7,U+6da9,U+6e0d-6e0e,U+6e83,U+6e85,U+6eaf,U+6ee4,U+6ee6,U+6f13,U+6f4d,U+709c,U+70bd,U+70c1,U+70db,U+70e8,U+70eb,U+718f,U+72b7,U+72f8,U+7315,U+73ae,U+73ba,U+73d1,U+73f2,U+7405,U+7410,U+7487,U+7574,U+759f,U+75a1,U+75ae-75af,U+75d2,U+75ea-75eb,U+75f4,U+762b,U+763e,U+766b,U+7682,U+76b1,U+76cf,U+772f,U+7741,U+7792,U+77e9,U+77eb,U+77f6,U+781a,U+783a,U+796f,U+7977,U+7985,U+79c3,U+79c6,U+79fd,U+7a8d,U+7a9c,U+7aa5-7aa6,U+7ad6,U+7b03,U+7b5b,U+7b5d,U+7bf1,U+7caa,U+7cd5,U+7eab-7ead,U+7eb6,U+7ec5,U+7eca,U+7ece,U+7ed1,U+7eda,U+7ede,U+7ee2,U+7ee5,U+7eee,U+7ef0,U+7ef7,U+7efd,U+7f00,U+7f09,U+7f0e,U+7f15,U+7f20,U+7f24,U+7f28,U+7f2a-7f2b,U+7f2d-7f2e,U+7f81,U+7fa1,U+7fd8,U+8038,U+803b,U+804b,U+80b4,U+80be,U+80e7,U+80eb,U+8110,U+814c,U+817b,U+82b8,U+82c7,U+82ce,U+82d4,U+830e,U+8327,U+835f,U+8367,U+836b,U+83b9-83ba,U+841d,U+8426,U+8471,U+84e6,U+8511,U+853c,U+8681-8682,U+86ee,U+8721,U+8747,U+8749,U+874e,U+889c,U+89c5,U+8ba5,U+8bb3-8bb4,U+8bb6-8bb7,U+8bbd,U+8bc0,U+8bd9,U+8be3,U+8be7,U+8beb-8bec,U+8bf2,U+8bf5,U+8bfd,U+8bff,U+8c06,U+8c0d-8c0e,U+8c19-8c1c,U+8c23-8c24,U+8c2c,U+8c34,U+8d1e,U+8d26,U+8d31,U+8d3b-8d3c,U+8d48,U+8d4a,U+8d4e,U+8d50,U+8d61,U+8e0a,U+8e52,U+8eaf,U+8f76,U+8f84,U+8f8a,U+8f8d,U+8f97,U+8f99,U+8fe9,U+8ff8,U+90ac,U+90f8,U+915d,U+9171,U+9489-948a,U+9499,U+949b,U+94a0,U+94a5,U+94a8,U+94ae,U+94b0,U+94b3,U+94be,U+94c0,U+94ce,U+94d0,U+94e2,U+94e8,U+94ec,U+94ee,U+94f2,U+94f5,U+94ff,U+9504,U+9508,U+950c,U+951a,U+9522,U+9524-9525,U+952d,U+952f-9530,U+9532,U+9535,U+9539,U+9540-9541,U+954c-954d,U+9550-9551,U+955b,U+956d,U+9576,U+95f5,U+95f7,U+95fa,U+9600,U+9647,U+9668,U+96b6,U+96bd,U+96cf,U+97e7,U+97ec,U+9885,U+988d,U+9890,U+98a0,U+98a4,U+996a,U+9975,U+997a,U+997f,U+9981,U+9985,U+998d,U+998f,U+9992,U+9a6d,U+9a6f,U+9a73-9a74,U+9a79,U+9a7c,U+9a7f,U+9a85-9a86,U+9a8b,U+9a8f,U+9aa5,U+9b13,U+9c7f,U+9c9f,U+9ca4,U+9cb8,U+9cbb,U+9cc4,U+9ccc,U+9cd6-9cd7,U+9cde,U+9e25-9e26,U+9e35,U+9e3d,U+9e43,U+9e4a,U+9f9f;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_b1143d.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4e11,U+4e1b,U+4e22,U+4e27,U+4ec6,U+4ed3,U+4eff,U+4f26,U+4f2a,U+4f5b,U+4fa6-4fa8,U+4fca,U+4fde,U+4fe9,U+4fed,U+5077,U+5151,U+5179,U+517d,U+5188,U+51af,U+51bb,U+51c0,U+51c9,U+51cc,U+51d1,U+51e4,U+51ed,U+51ef,U+51f6,U+522e,U+5239,U+5251,U+5265,U+5269,U+529d,U+52b2,U+52cb,U+535c,U+5362,U+5367,U+5389,U+5395,U+5398,U+53a2,U+53a8,U+53d9,U+53e0,U+53f9,U+540a,U+5415,U+541e,U+5446,U+54a8,U+54b8,U+54c4,U+54f2,U+5524,U+5582,U+55b7,U+5631,U+566a,U+5706,U+5708,U+5723,U+575b,U+575d,U+5784,U+5792,U+57a6,U+57ab,U+5899,U+58f3,U+58f6,U+5938-5939,U+5986,U+5988,U+59dc,U+5a18,U+5a31,U+5a74,U+5baa-5bab,U+5bff,U+5c18,U+5c1d,U+5c38,U+5c4f,U+5c61,U+5c82,U+5c9a,U+5ca9,U+5cad,U+5cb3,U+5d2d,U+5de2,U+5de9,U+5e05,U+5e1c,U+5e78,U+5e90,U+5e99,U+5e9e,U+5f03,U+5f25,U+5f2f,U+5f39,U+5f66,U+5fa1,U+5fc6,U+5fe7,U+604b,U+6052,U+6073,U+6076,U+607c,U+60a6,U+60ac,U+60e8-60e9,U+60ef,U+6108,U+6124,U+621a,U+6251,U+6263,U+626b,U+6270,U+629a-629b,U+62ac,U+62dc,U+62df,U+62e6,U+62e8,U+6321,U+6323-6324,U+6328,U+633d,U+635e,U+63fd,U+641c,U+643a,U+6447,U+644a,U+6491,U+654c,U+65cb,U+6606,U+664b,U+6682,U+6697,U+6734-6735,U+6740,U+6746,U+6760,U+67a2-67a3,U+67aa,U+67dc,U+680b,U+680f,U+6869,U+68a6,U+68cb,U+6982,U+6a2a,U+6b32,U+6bc1,U+6c64,U+6c9f,U+6ca7,U+6caa,U+6cc4,U+6cea,U+6cfc,U+6d12,U+6d45-6d47,U+6d51,U+6d53,U+6d8c,U+6d9b,U+6d9d,U+6da4,U+6dc0,U+6e0a,U+6e14,U+6e17,U+6e7f,U+6e89,U+6eda,U+6ede,U+6ee5,U+6ee8-6ee9,U+6f47,U+6f9c,U+6fd2,U+706d,U+7076,U+707f,U+7089,U+70ae,U+70bc,U+70c2,U+70e6-70e7,U+7115,U+7237,U+7275,U+727a,U+72b9,U+72ed-72ee,U+72f1,U+730e,U+732b,U+739b,U+73a9,U+743c,U+7476,U+74f6,U+7545,U+75c7,U+76d7,U+7750,U+77a9,U+7801,U+7816,U+7845,U+7855,U+788d,U+78b1,U+7978,U+7984,U+7a83,U+7a91,U+7a9d,U+7b0b,U+7b3c,U+7b4b,U+7bee,U+7ca4,U+7cd6,U+7ea0,U+7ea4,U+7eb1-7eb2,U+7eb5,U+7eb9,U+7ebd,U+7ed2,U+7ed8,U+7ee3,U+7eea,U+7ef3,U+7ef5,U+7ef8,U+7f05-7f06,U+7f14,U+7f18,U+7f1a,U+7f1d,U+7f34,U+7f62,U+8042,U+806a,U+80a0,U+80a4,U+80bf-80c1,U+80f6,U+8109,U+810f,U+8138,U+814a,U+817e,U+8206,U+820d,U+8230-8231,U+8273,U+829c,U+82a6,U+82cd,U+82f9,U+8346,U+8350,U+8361,U+83b1-83b2,U+8427,U+848b,U+84dd,U+8574,U+865a,U+866b,U+867e,U+8680,U+8695,U+8854,U+886c,U+88ad,U+88e4,U+89e6,U+8bbc,U+8bc8,U+8bca,U+8bd1,U+8bde,U+8be6,U+8bf1,U+8bf8,U+8bfa,U+8c05,U+8c0a,U+8c10,U+8c13,U+8c26,U+8c28,U+8c2d,U+8c31,U+8c37,U+8d1d,U+8d24,U+8d29-8d2a,U+8d2c,U+8d2e,U+8d3e-8d3f,U+8d41-8d43,U+8d4b-8d4c,U+8d4f,U+8d54,U+8d56,U+8d5a,U+8d60,U+8d62-8d63,U+8dfb,U+8e2a,U+8f67,U+8f69,U+8f70,U+8f74,U+8f7f,U+8f85,U+8f88,U+8f90-8f91,U+8f96,U+8f9e-8f9f,U+8fa9,U+8fc1,U+8fc8,U+8fdf,U+900a,U+9012,U+903b,U+903e,U+9065,U+90b9,U+90bb,U+90c1,U+9178,U+917f,U+91ca,U+9493,U+949e,U+94a6-94a7,U+94a9,U+94bb,U+94c3,U+94c5,U+94dc-94dd,U+94ed,U+94f8,U+94fa,U+94fe,U+9501,U+9505,U+950b,U+9510,U+9521,U+9523,U+953b,U+955c,U+95ea,U+95ef,U+95f2,U+95f8-95f9,U+95fd,U+9601,U+9605,U+960e,U+9610,U+9634,U+9655,U+9690,U+96c7,U+96fe,U+9709,U+97e6,U+97f5,U+9875-9877,U+987d,U+9882,U+9887-9888,U+9891,U+9896-9897,U+989c,U+98d8,U+9965,U+9971-9972,U+9976,U+997c,U+9988,U+9a70-9a71,U+9a76,U+9a7e,U+9a82,U+9a84,U+9a91,U+9a97,U+9a9a,U+9aa4,U+9c8d,U+9e1f,U+9e23,U+9e2d,U+9e3f,U+9e45,U+9e64,U+9e70,U+9f20,U+9f7f,U+9f9a;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_c728d8.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4e10,U+4e56,U+4ea2,U+4ea5,U+4ec3,U+4f36,U+4f3a,U+4f5f,U+4f6c,U+4f70,U+4f84,U+4f88,U+4f8d,U+4f97,U+5014,U+501a,U+5029,U+504c,U+516e,U+5197,U+51a5,U+51f8,U+5201,U+524c,U+527f,U+5288,U+52fa,U+5315,U+5321,U+5352,U+5366,U+53e8-53e9,U+5406,U+540f,U+543c,U+5471,U+5480,U+5495,U+549a,U+54b3,U+54c7,U+54e6,U+54ee,U+54fc,U+5501,U+556a,U+557c,U+5583,U+55dc,U+55e3,U+5632,U+5669,U+566c,U+5676,U+56b7,U+56bc,U+56da,U+56e4,U+56ff,U+5703,U+5729,U+57a2-57a3,U+58d1,U+5919,U+592d,U+592f,U+5944,U+5955,U+595a,U+5983,U+598d,U+5996,U+59ca,U+59e5,U+5a13,U+5a25,U+5a77,U+5a7f,U+5a9b,U+5ab2,U+5b09,U+5b70,U+5b75,U+5b7a,U+5bd0,U+5bf0,U+5c41,U+5c79,U+5c91,U+5c94,U+5ced,U+5d69,U+5e16,U+5e4c,U+5eb6,U+5f08,U+5f1b,U+5f27,U+5f3c,U+5ffb,U+6020,U+602f,U+604d,U+6055,U+606c,U+608d,U+60cb,U+60e6,U+60f6,U+618b,U+61a7-61a8,U+61ac,U+61cb,U+620c-620e,U+6233,U+6249,U+6273,U+6289,U+62c4,U+62c7,U+62ce,U+62d9,U+62ed-62ee,U+62f4,U+62f7,U+6345,U+634d-634e,U+6382,U+6396,U+63c9,U+63ea,U+6413,U+6452,U+645e,U+6479,U+6482,U+6487,U+64ac,U+64ce,U+64d2,U+6556,U+659f,U+65a1,U+660a,U+6615,U+664f,U+667e,U+66e6,U+6726,U+6756,U+675e,U+67c4,U+67da,U+6853,U+6897,U+68a2,U+68e3,U+68fa,U+693f,U+6942,U+695e,U+6995,U+69a8,U+69ad,U+69b4,U+69cc,U+6a44,U+6a59,U+6a80,U+6b83,U+6b86,U+6b89,U+6bcb,U+6bd3,U+6c30,U+6c40,U+6c5e,U+6c85,U+6cad,U+6cb1,U+6cbd,U+6cf1,U+6d19,U+6d31,U+6d85,U+6d93,U+6daa,U+6dae,U+6dbf,U+6dc6-6dc7,U+6e32,U+6e3a,U+6e43-6e44,U+6e4d,U+6e5f,U+6eba,U+6ec1,U+6f31,U+6f3e,U+6f5e,U+6f7a,U+6f7c,U+6f88,U+6f8e,U+6fee,U+701b,U+7078,U+7095,U+7099,U+70ab,U+70d9,U+70f7,U+70fd,U+7109,U+715e,U+717d,U+7184,U+71a8,U+71ce,U+71ee,U+722a,U+7235,U+7239,U+7252,U+72c4,U+72e1,U+72e9,U+733f,U+73c0,U+73c2,U+7409,U+7435-7436,U+7459-745a,U+747e,U+748b,U+749c,U+749e,U+74a8,U+74e2-74e3,U+7504,U+752c-752d,U+7599-759a,U+75a4,U+75ca,U+75f0,U+75f9,U+7600-7601,U+761f-7620,U+7629,U+768b,U+7693,U+7738,U+773a,U+776b,U+777f,U+7785,U+778e,U+77a5,U+77aa,U+77b0,U+77bf,U+7852,U+78be,U+78d5,U+78fa,U+7934,U+7940,U+7948,U+795b,U+7960,U+79a7,U+79ba,U+7a20,U+7a96,U+7b19,U+7b28,U+7b50,U+7b71,U+7b77,U+7b94,U+7bc6,U+7c27,U+7c38,U+7c95,U+7cb1,U+7ce0,U+7d0a,U+7d6e,U+7f79,U+7f9a,U+7fcc,U+8046,U+8098,U+80da,U+80e5,U+80f0,U+80f3,U+80fa,U+812f,U+8148,U+8165,U+818a,U+819b,U+81b3,U+81ba,U+81e7,U+8235,U+8237-8238,U+828b,U+8299,U+82ae,U+82b7,U+82d3,U+8301,U+831c,U+8338-8339,U+837b-837c,U+8386,U+8393,U+8398,U+83bd,U+83c1,U+83cf,U+83e0,U+83e9,U+8475,U+84bf,U+84d3,U+85c9,U+85d5,U+85dc,U+85e9,U+8654,U+86db,U+86df,U+86fe,U+8712,U+871a,U+873f,U+8757,U+8783,U+8859,U+888d,U+88a4,U+88f3-88f4,U+8910,U+891a,U+8925,U+8d66,U+8db4,U+8dda,U+8de4,U+8e1e,U+8e4a,U+8e66,U+8e6c,U+8fe5,U+901e,U+9083,U+90b3,U+90b8,U+90e1,U+90f4,U+9119,U+912f,U+9131,U+914c,U+915a,U+9163,U+9165,U+9170,U+91c9,U+91dc,U+962a,U+965b,U+9685,U+9698,U+96cc-96cd,U+96ef,U+96f9,U+9704,U+9713,U+9773-9774,U+9776,U+9ab8,U+9e8b,U+9e92,U+9eef;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_8ea124.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+4e07,U+4e0e,U+4e13,U+4e1a,U+4e1c-4e1d,U+4e24-4e25,U+4e2a,U+4e30,U+4e34,U+4e3a,U+4e3d-4e3e,U+4e48-4e49,U+4e4c,U+4e50,U+4e54,U+4e60-4e61,U+4e66,U+4e70-4e71,U+4e86,U+4e89,U+4e8e-4e8f,U+4e91,U+4e9a,U+4ea7,U+4ea9,U+4eb2,U+4ebf,U+4ec5,U+4ece,U+4eea,U+4eec,U+4ef7,U+4f17-4f1a,U+4f1f-4f20,U+4f24,U+4f53,U+4f59,U+4f60,U+4fee,U+501f,U+503a,U+503e,U+507f,U+50a8,U+513f,U+514b,U+515a,U+5170,U+5173-5174,U+517b,U+5185,U+518c,U+5199,U+519b-519c,U+51ac,U+51b2-51b3,U+51b5,U+51c6,U+51cf,U+51e0,U+51fa-51fb,U+5212,U+5218-521b,U+522b,U+5236,U+5242,U+5267,U+529e,U+52a1,U+52a8,U+52b1,U+52b3,U+52bf,U+533a-533b,U+5343,U+534e-534f,U+5355-5356,U+5360,U+536b,U+5373-5374,U+5377,U+5385-5386,U+538b,U+53a6,U+53bf,U+53c2,U+53cc,U+53d1,U+53d8,U+53ea,U+53f6-53f7,U+5403,U+5408,U+540c,U+540e,U+5417,U+5428,U+542c,U+542f,U+5434,U+5458,U+54cd,U+56de,U+56e2,U+56ed,U+56f0,U+56f4,U+56fd-56fe,U+573a,U+574f,U+5757,U+575a,U+58ee,U+58f0,U+5904,U+5907,U+590d,U+591f,U+592b,U+5934,U+593a,U+594b,U+5956,U+5965,U+5987,U+5b59,U+5b66,U+5b81,U+5b83,U+5b9d-5b9e,U+5ba1,U+5bb6,U+5bbd-5bbe,U+5bf9,U+5bfb-5bfc,U+5c06,U+5c14,U+5c3d,U+5c40,U+5c42,U+5c4a,U+5c5e,U+5c81,U+5c97,U+5c9b,U+5ce1,U+5cf0,U+5de8,U+5e01,U+5e03,U+5e08,U+5e10,U+5e26,U+5e2d-5e2e,U+5e72,U+5e76,U+5e7f,U+5e84,U+5e86,U+5e8a,U+5e93-5e94,U+5e9f,U+5f00,U+5f02,U+5f20,U+5f3a,U+5f52-5f53,U+5f55,U+5f69,U+5f7b,U+5f81,U+5f84,U+5fd7,U+5ff5,U+6000-6001,U+603b,U+60ca,U+613f,U+620f,U+6218,U+6237,U+624d-624e,U+6258,U+6267,U+6269,U+626c,U+6298,U+62a2,U+62a4-62a5,U+62b5,U+62c5,U+62e5,U+62e9,U+6302,U+6325,U+635f,U+6362,U+636e,U+6444,U+6446,U+6559,U+6570,U+6597,U+65ad,U+65d7,U+65e0,U+65e2,U+65e7,U+65f6,U+663e,U+6653,U+672f,U+6731,U+673a,U+6742-6743,U+6761,U+6765,U+6768,U+676f-6770,U+677e-677f,U+6781,U+6784,U+679c,U+67e5,U+6807,U+6811,U+6837-6838,U+6863,U+6865,U+6881,U+68c0,U+697c,U+6b22,U+6b27,U+6b8b,U+6bd5,U+6c14,U+6c47,U+6c49,U+6c61,U+6c88,U+6ca1,U+6ce8,U+6cfd,U+6d01,U+6d4b,U+6d4e,U+6d89,U+6da6,U+6da8,U+6e10,U+6e29,U+6e38,U+6e7e,U+6ee1,U+6f5c,U+706f,U+7075,U+707e,U+70b9,U+70df,U+70ed,U+7231,U+72b6,U+72ec,U+732a,U+732e,U+73af-73b0,U+7535,U+753b,U+7597,U+76d0-76d1,U+76d6,U+76d8,U+771f,U+7740,U+77ff,U+7840,U+786e,U+793c,U+79bb,U+79c1,U+79cb,U+79cd,U+79d8,U+79ef-79f0,U+7a0e,U+7a33,U+7a77,U+7ade,U+7b14,U+7b51,U+7b79,U+7b7e,U+7b80,U+7c7b,U+7cae,U+7d27,U+7d2f,U+7ea2,U+7ea6-7ea7,U+7eaa,U+7eaf,U+7eb3,U+7eb7-7eb8,U+7eba,U+7ebf,U+7ec3-7ec4,U+7ec6-7ec8,U+7ecd,U+7ecf,U+7ed3,U+7ed5,U+7ed9,U+7edc-7edd,U+7edf,U+7ee7,U+7ee9,U+7eed,U+7ef4,U+7efc,U+7eff,U+7f13,U+7f16,U+7f29,U+7f51,U+7f57,U+7f5a,U+7fa4,U+804c,U+8054,U+8083,U+80c6,U+80cc,U+80dc,U+8111,U+811a,U+8131,U+81f4,U+8270,U+827a,U+8282,U+82cf,U+8303,U+8363,U+836f,U+83b7,U+8425,U+8428,U+8651,U+867d,U+8865,U+8868,U+88c5,U+89c1-89c2,U+89c4,U+89c6,U+89c8-89c9,U+8a89,U+8ba1-8ba2,U+8ba4,U+8ba8-8ba9,U+8bad-8bb0,U+8bb2,U+8bb8,U+8bba,U+8bbe-8bbf,U+8bc1,U+8bc4,U+8bc6,U+8bc9,U+8bcd,U+8bd5,U+8bd7,U+8bda,U+8bdd,U+8be2,U+8be5,U+8bed,U+8bef,U+8bf4,U+8bf7,U+8bfb,U+8bfe,U+8c01,U+8c03,U+8c08,U+8c0b,U+8c22,U+8d1f,U+8d21-8d23,U+8d25,U+8d27-8d28,U+8d2b,U+8d2d,U+8d2f,U+8d34-8d35,U+8d37-8d3a,U+8d44,U+8d5b,U+8d5e,U+8d75-8d76,U+8d8b,U+8dc3,U+8df5,U+8f66,U+8f68,U+8f6c,U+8f6e-8f6f,U+8f7b,U+8f7d,U+8f83,U+8f86,U+8f89,U+8f93,U+8fb9,U+8fbd-8fbe,U+8fc7,U+8fd0,U+8fd8-8fd9,U+8fdb-8fde,U+8ff9,U+9002,U+9009,U+9057,U+9093,U+90ae,U+90d1,U+91cc,U+9274,U+9488,U+949f,U+94a2,U+94b1,U+94c1,U+94f6,U+9500,U+9519,U+9526,U+952e,U+9547,U+957f,U+95e8,U+95ed-95ee,U+95f4,U+95fb,U+9614,U+961f,U+9633,U+9635-9636,U+9645-9646,U+9648,U+9669,U+968f,U+96be,U+9759,U+97e9,U+9879-987b,U+987e-987f,U+9881,U+9884,U+9886,U+9898,U+989d,U+98ce,U+98de,U+996d-996e,U+9970,U+9986,U+9a6c,U+9a7b,U+9a8c,U+9c7c,U+9c81,U+9c9c,U+9e21,U+9e4f,U+9ea6,U+9ec4,U+9ed1,U+9f50,U+9f84,U+9f99;
|
||||
}
|
||||
@font-face {
|
||||
font-family:"HarmonyOS Sans SC";
|
||||
src:local("HarmonyOS Sans SC Bold"),local("HarmonyOS Sans SC"),url("./Bold_c614c1.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
unicode-range:U+3041-3094,U+309b-309e,U+30a1-30fe;
|
||||
}
|
||||
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.
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.
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.
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.
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.
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.
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.
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.
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.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user